{ outputs, config, lib, pkgs, ... }:
let
# Dependencies
cat = "${pkgs.coreutils}/bin/cat";
cut = "${pkgs.coreutils}/bin/cut";
find = "${pkgs.findutils}/bin/find";
grep = "${pkgs.gnugrep}/bin/grep";
pgrep = "${pkgs.procps}/bin/pgrep";
tail = "${pkgs.coreutils}/bin/tail";
wc = "${pkgs.coreutils}/bin/wc";
xargs = "${pkgs.findutils}/bin/xargs";
timeout = "${pkgs.coreutils}/bin/timeout";
ping = "${pkgs.iputils}/bin/ping";
jq = "${pkgs.jq}/bin/jq";
gamemoded = "${pkgs.gamemode}/bin/gamemoded";
systemctl = "${pkgs.systemd}/bin/systemctl";
journalctl = "${pkgs.systemd}/bin/journalctl";
playerctl = "${pkgs.playerctl}/bin/playerctl";
playerctld = "${pkgs.playerctl}/bin/playerctld";
pavucontrol = "${pkgs.pavucontrol}/bin/pavucontrol";
wofi = "${pkgs.wofi}/bin/wofi";
# Function to simplify making waybar outputs
jsonOutput = name: { pre ? "", text ? "", tooltip ? "", alt ? "", class ? "", percentage ? "" }: "${pkgs.writeShellScriptBin "waybar-${name}" ''
set -euo pipefail
${pre}
${jq} -cn \
--arg text "${text}" \
--arg tooltip "${tooltip}" \
--arg alt "${alt}" \
--arg class "${class}" \
--arg percentage "${percentage}" \
'{text:$text,tooltip:$tooltip,alt:$alt,class:$class,percentage:$percentage}'
''}/bin/waybar-${name}";
in
{
programs.waybar = {
enable = true;
package = pkgs.waybar.overrideAttrs (oa: {
mesonFlags = (oa.mesonFlags or [ ]) ++ [ "-Dexperimental=true" ];
});
systemd.enable = true;
settings = {
primary = {
mode = "dock";
layer = "top";
height = 40;
margin = "6";
position = "top";
modules-left = [
"custom/menu"
] ++ (lib.optionals config.wayland.windowManager.sway.enable [
"sway/workspaces"
"sway/mode"
]) ++ (lib.optionals config.wayland.windowManager.hyprland.enable [
"hyprland/workspaces"
"hyprland/submap"
]) ++ [
"custom/currentplayer"
"custom/player"
];
modules-center = [
"pulseaudio"
"battery"
"clock"
"custom/unread-mail"
"custom/gpg-agent"
];
modules-right = [
"network"
"custom/tailscale-ping"
"custom/gamemode"
# TODO: currently broken for some reason
# "custom/gammastep"
"tray"
"custom/hostname"
];
clock = {
interval = 1;
format = "{:%d/%m %H:%M:%S}";
format-alt = "{:%Y-%m-%d %H:%M:%S %z}";
on-click-left = "mode";
tooltip-format = ''
{:%Y %B}
{calendar}'';
};
pulseaudio = {
format = "{icon} {volume}%";
format-muted = " 0%";
format-icons = {
headphone = "";
headset = "";
portable = "";
default = [ "" "" "" ];
};
on-click = pavucontrol;
};
idle_inhibitor = {
format = "{icon}";
format-icons = {
activated = "";
deactivated = "";
};
};
battery = {
bat = "BAT0";
interval = 10;
format-icons = [ "" "" "" "" "" "" "" "" "" "" ];
format = "{icon} {capacity}%";
format-charging = " {capacity}%";
onclick = "";
};
"sway/window" = {
max-length = 20;
};
network = {
interval = 3;
format-wifi = " {essid}";
format-ethernet = " Connected";
format-disconnected = "";
tooltip-format = ''
{ifname}
{ipaddr}/{cidr}
Up: {bandwidthUpBits}
Down: {bandwidthDownBits}'';
on-click = "";
};
"custom/tailscale-ping" = {
interval = 10;
return-type = "json";
exec =
let
inherit (builtins) concatStringsSep attrNames;
hosts = attrNames outputs.nixosConfigurations;
homeMachine = "merope";
remoteMachine = "alcyone";
in
jsonOutput "tailscale-ping" {
# Build variables for each host
pre = ''
set -o pipefail
${concatStringsSep "\n" (map (host: ''
ping_${host}="$(${timeout} 2 ${ping} -c 1 -q ${host} 2>/dev/null | ${tail} -1 | ${cut} -d '/' -f5 | ${cut} -d '.' -f1)ms" || ping_${host}="Disconnected"
'') hosts)}
'';
# Access a remote machine's and a home machine's ping
text = " $ping_${remoteMachine} / $ping_${homeMachine}";
# Show pings from all machines
tooltip = concatStringsSep "\n" (map (host: "${host}: $ping_${host}") hosts);
};
format = "{}";
on-click = "";
};
"custom/menu" = {
return-type = "json";
exec = jsonOutput "menu" {
text = "";
tooltip = ''$(${cat} /etc/os-release | ${grep} PRETTY_NAME | ${cut} -d '"' -f2)'';
};
on-click = "${wofi} -S drun -x 10 -y 10 -W 25% -H 60%";
};
"custom/hostname" = {
exec = "echo $USER@$HOSTNAME";
};
"custom/unread-mail" = {
interval = 5;
return-type = "json";
exec = jsonOutput "unread-mail" {
pre = ''
count=$(${find} ~/Mail/*/Inbox/new -type f | ${wc} -l)
if ${pgrep} mbsync &>/dev/null; then
status="syncing"
else if [ "$count" == "0" ]; then
status="read"
else
status="unread"
fi
fi
'';
text = "$count";
alt = "$status";
};
format = "{icon} ({})";
format-icons = {
"read" = "";
"unread" = "";
"syncing" = "";
};
};
"custom/gpg-agent" = {
interval = 2;
return-type = "json";
exec =
let gpgCmds = import ../../../cli/gpg-commands.nix { inherit pkgs; };
in
jsonOutput "gpg-agent" {
pre = ''status=$(${gpgCmds.isUnlocked} && echo "unlocked" || echo "locked")'';
alt = "$status";
tooltip = "GPG is $status";
};
format = "{icon}";
format-icons = {
"locked" = "";
"unlocked" = "";
};
on-click = "";
};
"custom/gamemode" = {
exec-if = "${gamemoded} --status | ${grep} 'is active' -q";
interval = 2;
return-type = "json";
exec = jsonOutput "gamemode" {
tooltip = "Gamemode is active";
};
format = " ";
};
"custom/gammastep" = {
interval = 5;
return-type = "json";
exec = jsonOutput "gammastep" {
pre = ''
if unit_status="$(${systemctl} --user is-active gammastep)"; then
status="$unit_status ($(${journalctl} --user -u gammastep.service -g 'Period: ' | ${tail} -1 | ${cut} -d ':' -f6 | ${xargs}))"
else
status="$unit_status"
fi
'';
alt = "\${status:-inactive}";
tooltip = "Gammastep is $status";
};
format = "{icon}";
format-icons = {
"activating" = " ";
"deactivating" = " ";
"inactive" = "? ";
"active (Night)" = " ";
"active (Nighttime)" = " ";
"active (Transition (Night)" = " ";
"active (Transition (Nighttime)" = " ";
"active (Day)" = " ";
"active (Daytime)" = " ";
"active (Transition (Day)" = " ";
"active (Transition (Daytime)" = " ";
};
on-click = "${systemctl} --user is-active gammastep && ${systemctl} --user stop gammastep || ${systemctl} --user start gammastep";
};
"custom/currentplayer" = {
interval = 2;
return-type = "json";
exec = jsonOutput "currentplayer" {
pre = ''
player="$(${playerctl} status -f "{{playerName}}" 2>/dev/null || echo "No player active" | ${cut} -d '.' -f1)"
count="$(${playerctl} -l | ${wc} -l)"
if ((count > 1)); then
more=" +$((count - 1))"
else
more=""
fi
'';
alt = "$player";
tooltip = "$player ($count available)";
text = "$more";
};
format = "{icon}{}";
format-icons = {
"No player active" = " ";
"Celluloid" = " ";
"spotify" = " ";
"ncspot" = " ";
"qutebrowser" = " ";
"firefox" = " ";
"discord" = " ";
"sublimemusic" = " ";
"kdeconnect" = " ";
"chromium" = " ";
};
on-click = "${playerctld} shift";
on-click-right = "${playerctld} unshift";
};
"custom/player" = {
exec-if = "${playerctl} status";
exec = ''${playerctl} metadata --format '{"text": "{{title}} - {{artist}}", "alt": "{{status}}", "tooltip": "{{title}} - {{artist}} ({{album}})"}' '';
return-type = "json";
interval = 2;
max-length = 30;
format = "{icon} {}";
format-icons = {
"Playing" = "";
"Paused" = " ";
"Stopped" = "";
};
on-click = "${playerctl} play-pause";
};
};
};
# Cheatsheet:
# x -> all sides
# x y -> vertical, horizontal
# x y z -> top, horizontal, bottom
# w x y z -> top, right, bottom, left
style = let inherit (config.colorscheme) colors; in /* css */ ''
* {
font-family: ${config.fontProfiles.regular.family}, ${config.fontProfiles.monospace.family};
font-size: 12pt;
padding: 0 8px;
}
.modules-right {
margin-right: -15px;
}
.modules-left {
margin-left: -15px;
}
window#waybar.top {
opacity: 0.95;
padding: 0;
background-color: #${colors.base00};
border: 2px solid #${colors.base0C};
border-radius: 10px;
}
window#waybar.bottom {
opacity: 0.90;
background-color: #${colors.base00};
border: 2px solid #${colors.base0C};
border-radius: 10px;
}
window#waybar {
color: #${colors.base05};
}
#workspaces button {
background-color: #${colors.base01};
color: #${colors.base05};
padding: 5px 1px;
margin: 3px 0;
}
#workspaces button.hidden {
background-color: #${colors.base00};
color: #${colors.base04};
}
#workspaces button.focused,
#workspaces button.active {
background-color: #${colors.base0A};
color: #${colors.base00};
}
#clock {
background-color: #${colors.base0C};
color: #${colors.base00};
padding-left: 15px;
padding-right: 15px;
margin-top: 0;
margin-bottom: 0;
border-radius: 10px;
}
#custom-menu {
background-color: #${colors.base0C};
color: #${colors.base00};
padding-left: 15px;
padding-right: 22px;
margin: 0;
border-radius: 10px;
}
#custom-hostname {
background-color: #${colors.base0C};
color: #${colors.base00};
padding-left: 15px;
padding-right: 18px;
margin-right: 0;
margin-top: 0;
margin-bottom: 0;
border-radius: 10px;
}
#custom-currentplayer {
padding-right: 0;
}
#tray {
color: #${colors.base05};
}
'';
};
}