dotfiles/nixos/configs/gui/waybar.nix
2024-11-16 20:27:38 -07:00

507 lines
14 KiB
Nix

#
# Use Waybar with our GUI.
#
# vim: et:ts=2:sw=2:
{
lib,
pkgs,
deprekages,
config,
...
}:
let
hostname = config.networking.hostName;
isSmallScreen = (hostname == "utol");
in
{
# Pull in some dependencies for our bar.
environment.systemPackages = with pkgs; [ networkmanagerapplet ];
# Enable the yubikey touch detector, for our icon.
programs.yubikey-touch-detector.enable = true;
#
# Set up our waybar configuration in home-manager.
#
home-manager.users.deprekated =
{ pkgs, config, ... }:
let
icons = rec {
calendar = "󰃭 ";
clock = " ";
puclock = "󱑆";
cpu = "";
ram = "";
battery.charging = "󱐋";
battery.horizontal = [
" "
" "
" "
" "
" "
];
battery.vertical = [
"󰁺"
"󰁻"
"󰁼"
"󰁽"
"󰁾"
"󰁿"
"󰂀"
"󰂁"
"󰂂"
"󰁹"
];
battery.levels = battery.vertical;
network.disconnected = "󰤮 ";
network.ethernet = "󰈀 ";
network.strength = [
"󰤟 "
"󰤢 "
"󰤥 "
"󰤨 "
];
bluetooth.on = "󰂯";
bluetooth.off = "󰂲";
bluetooth.battery = "󰥉";
volume.source = "󱄠";
volume.muted = "󰝟";
volume.levels = [
"󰕿"
"󰖀"
"󰕾"
];
idle = {
on = "<span color='#${colors.red}'>󰅶</span>";
off = "󰾪";
};
vpn = "󰌆 ";
music = {
playing = "";
paused = "";
stopped = "";
missing = "󰌙";
};
speed = {
slow = "󰾆";
medium = "󰾅";
fast = "󰓅";
};
};
colors = config.lib.stylix.colors;
# Create a TODO-list handler.
waybar-todos = pkgs.writeScriptBin "waybar-todos" ''
#!${pkgs.python3}/bin/python3
import subprocess
import json
def get_count(*filter):
return int(subprocess.run(['${pkgs.taskwarrior3}/bin/task', 'count', *filter], capture_output=True, text=True).stdout)
def get_tasks(*filter):
return subprocess.run(['${pkgs.taskwarrior3}/bin/task', 'ls', *filter], capture_output=True, text=True).stdout
priority = get_count("(+TODAY or +OVERDUE)", "priority:H")
due_today = get_count("+TODAY")
overdue = get_count("+OVERDUE")
overdue_color = ' color="#dc322f" ' if overdue else ' color="#93a1a1" '
overdue_text = "<span {overdue_color}> {overdue} </span>".format(overdue=overdue, overdue_color=overdue_color) if overdue else ""
priority_text = ' 󱕈 {priority} '.format(priority=priority) if priority else ""
today_color = "" if due_today else ' color="#93a1a1" '
data = {}
data['text'] = "{overdue_text}{priority_text}<span {today_color}>󰃶 {due_today} </span>".format(overdue_text=overdue_text, due_today=due_today, today_color=today_color, priority_text=priority_text)
data['tooltip'] = get_tasks("(+TODAY or +OVERDUE)")
print(json.dumps(data))
'';
# From the Waybar wiki.
waybar-events = pkgs.writeScriptBin "waybar-events" ''
#!${pkgs.python3}/bin/python3
import calendar
import subprocess
import datetime
import json
import html
import sys
data = {}
today = datetime.date.today().strftime("%Y-%m-%d")
next_week = (datetime.date.today() +
datetime.timedelta(days=10)).strftime("%Y-%m-%d")
tomorrow = (datetime.date.today() +
datetime.timedelta(days=1)).strftime("%Y-%m-%d")
human_events = subprocess.check_output('khal list now ' + next_week, shell=True).decode('utf-8')
machine_events = subprocess.check_output('khal list now ' + next_week + ' -d "Holidays in United States" --once -f "{title}|{start-date}|{start-time}" -df ""', shell=True).decode('utf-8')
if not machine_events:
sys.exit(0)
next_event = machine_events.split("\n")[0]
event, date, time = next_event.split('|')
# Generate our hover-over text.
lines = human_events.split("\n")
new_lines = []
for line in lines:
if line.startswith("To hide observances") or (line == ""):
continue
clean_line = html.escape(line).split(" ::")[0]
if len(clean_line) and not clean_line[0] in ['0', '1', '2']:
clean_line = "\n<b><span color='#${colors.blue}'>"+clean_line+"</span></b>" if (',' in clean_line) else clean_line
new_lines.append(clean_line)
tooltip = "\n".join(new_lines).strip()
if tomorrow in date:
date = "tomorrow"
else:
weekday = datetime.datetime.fromisoformat(date).weekday()
date = calendar.day_name[weekday].lower()
# Add time suffixes when appropriate.
time_suffix = ""
if time:
time_suffix = " @ " + time
date_suffix = ""
if today not in date:
date_suffix = ", " + date
# In the typical case, display the next event.
data['text'] = html.escape(" " + event[0:30] + date_suffix + time_suffix)
# Display our nice, human-readable event ouptut on hover.
data['tooltip'] = tooltip
# Finally, send the data to Waybar.
print(json.dumps(data))
'';
waybar-title = pkgs.writeScriptBin "waybar-title" ''
#!${pkgs.bash}/bin/bash
niri msg --json focused-window | jq -c '{text: (if ((.title | length) > 40) then (.title[:38] + "") else .title end), tooltip: .app_id}' | sed 's/&[ $]/\&amp; /g'
'';
waybar-puck-countdown = pkgs.writeScriptBin "waybar-puck-countdown" ''
#!${pkgs.python3}/bin/python3
import json
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
puck_day = "2024-12-11 13:30:00-07:00"
puck_time = datetime.fromisoformat(puck_day)
until = puck_time - now
response = {
"text": f" 󰧒 {until.days}d {until.seconds // 60 // 60}h",
"tooltip": f"days until {puck_day}"
}
print(json.dumps(response))
'';
in
{
#
# Core Waybar settings.
#
programs.waybar = {
enable = true;
systemd.enable = true;
};
programs.waybar.settings.mainBar = {
layer = "top";
#mode = "overlay";
modules-left = [
"clock"
"clock#otherzone"
"mpd"
];
modules-center = [
"custom/title"
"wlr/taskbar"
];
modules-right = [
"tray"
"custom/events"
"privacy"
"custom/yubikey"
#"wireplumber"
"custom/puckdown"
"battery"
];
battery = {
interval = 5;
format = "{icon} {capacity}%";
format-charging = "{icon} {capacity}% ${icons.battery.charging}";
format-icons = icons.battery.levels;
states.warning = 30;
states.critical = 15;
};
cpu = {
format = "${icons.cpu} {usage}% @ {avg_frequency:0.1f}GHz";
};
memory = {
format = "${icons.ram} {used:0.1f}G / {total:0.1f}G";
};
clock = {
interval = 1;
format = "${icons.clock} {:%H:%M %A, %B %d %Z}";
tooltip-format = "<tt>${icons.calendar}<u><b> {:%a, %Y-%m-%d}</b></u>\n\n{calendar}</tt>";
calendar = {
mode = "month";
format = {
weeks = "<b>W{}</b>";
months = "<span color='#${colors.magenta}'><b>{}</b></span>";
days = "<span color='#${colors.blue}'><b>{}</b></span>";
weekdays = "<span color='#${colors.cyan}'><b>{}</b></span>";
today = "<span color='#${colors.green}'><b>{}</b></span>";
};
};
};
"clock#otherzone" = {
interval = 1;
format = "${icons.puclock} {:%H:%M %Z}";
timezone = "Europe/Amsterdam";
tooltip-format = ''
<b><u>Homosexuality Statistics</u></b>
Homosexuality is <b><span color='#${colors.green}'>online</span></b>.
Homosexuality levels are <b><span color='#${colors.red}'>CRITICAL</span></b>.
'';
};
network = {
on-click = "${pkgs.networkmanagerapplet}/bin/nm-connection-editor";
tooltip = false;
format-disconnected = icons.network.disconnected;
format-ethernet = "${icons.network.ethernet}";
format-wifi = "{icon} {essid}";
format-icons = icons.network.strength;
};
bluetooth = {
format = "{icon}";
format-disabled = "{icon}";
format-icons = {
inherit (icons.bluetooth) on off;
connected = icons.bluetooth.on;
};
format-connected = "{icon} {device_alias}";
};
"bluetooth#battery" = {
format = "";
format-connected-battery = "${icons.bluetooth.battery} {device_battery_percentage}%";
};
wireplumber = {
on-click = "${pkgs.pavucontrol}/bin/pavucontrol -t 1";
format = "{icon} {volume}%";
tooltip-format = "${icons.volume.source} {node_name}";
format-muted = "${icons.volume.muted}";
format-icons = icons.volume.levels;
reverse-scrolling = 1;
};
# Displays an icon when our yubikey is waiting for touch.
"custom/yubikey" = {
exec = ./waybar-yubikey.sh;
return-type = "json";
};
"custom/events" = {
exec = "${waybar-events}/bin/waybar-events";
format = "{}";
tooltip = true;
return-type = "json";
interval = 60;
on-click = "${pkgs.wezterm}/bin/wezterm start ikhal";
};
"custom/title" = {
exec = "${waybar-title}/bin/waybar-title";
format = "{}";
tooltip = true;
return-type = "json";
interval = 1;
};
"custom/puckdown" = {
exec = "${waybar-puck-countdown}/bin/waybar-puck-countdown";
format = "{}";
tooltip = true;
return-type = "json";
interval = 60 * 5;
};
"wlr/taskbar" = {
format = "{icon}";
tooltip-format = "{app_id}";
on-click = "activate";
};
tray = {
icon-size = 21;
spacing = 10;
};
# Display indicators when we're sharing video or audio.
privacy = {
icon-spacing = 4;
icon-size = 18;
transition-duration = 250;
modules = [
{
type = "screenshare";
tooltip = false;
tooltip-icon-size = 24;
}
{
type = "audio-out";
tooltip = false;
tooltip-icon-size = 24;
}
{
type = "audio-in";
tooltip = false;
tooltip-icon-size = 24;
}
];
};
mpd = {
on-click = "mpc toggle";
format = "${icons.music.playing} {artist} - {title}";
format-paused = "${icons.music.paused} {artist} - {title}";
format-stopped = "";
format-disconnected = "${icons.music.missing}";
tooltip-format = "${icons.music.playing} {artist} - {title}";
max-length = 60;
};
idle_inhibitor = {
format = "{icon}";
format-icons = {
activated = icons.idle.on;
deactivated = icons.idle.off;
};
tooltip-format-activated = "System <span color='#${colors.orange}'><b>blocked</b></span> from going idle.";
tooltip-format-deactivated = "System <span color='#${colors.green}'><b>allowed</b></span> to go idle.";
};
};
stylix.targets.waybar.enable = false;
programs.waybar.style =
let
colors = config.lib.stylix.colors;
modules = s: "${s ".modules-left"}, ${s ".modules-center"}, ${s ".modules-right"}";
module = s: modules (m: "${m} > ${s} > *");
in
''
* {
border: none;
font-family: ${config.stylix.fonts.monospace.name};
font-weight: 400;
font-size: ${if isSmallScreen then "17" else toString config.stylix.fonts.sizes.desktop}px;
color: #${colors.base06};
}
window#waybar {
background: #${colors.base00};
}
#taskbar button {
padding: 0 0.4em;
}
#taskbar button.active {
background: #${colors.base01};
}
#tray image,
#taskbar image {
-gtk-icon-transform: scale(0.75);
margin: -5px -4px;
padding: 0px 2px;
}
${modules lib.id} {
margin: 0;
}
${module "*"} {
padding: 0 5px;
margin: 0;
font-size: 75%;
}
${module ":not(:first-child)"} {
border-left: 1px solid #${colors.base01};
}
${module ":not(:last-child)"} {
border-right: 1px solid #${colors.base01};
}
#battery.charging {
color: #${colors.green};
}
#battery.warning:not(.charging) {
color: #${colors.yellow};
}
#battery.critical:not(.charging) {
animation: critical-blink steps(8) 1s infinite alternate;
}
@keyframes critical-blink {
to {
color: #${colors.red};
}
}
'';
#
# Restart waybar when niri starts.
#
programs.niri.settings.spawn-at-startup = [
{
command = [
"systemctl"
"--user"
"restart"
"waybar"
];
}
{ command = [ "nm-applet" ]; }
];
};
}