# # 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 = "󰅶"; 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 = " {overdue} ".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}󰃶 {due_today} ".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"+clean_line+"" 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/&[ $]/\& /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 = "${icons.calendar} {:%a, %Y-%m-%d}\n\n{calendar}"; calendar = { mode = "month"; format = { weeks = "W{}"; months = "{}"; days = "{}"; weekdays = "{}"; today = "{}"; }; }; }; "clock#otherzone" = { interval = 1; format = "${icons.puclock} {:%H:%M %Z}"; timezone = "Europe/Amsterdam"; tooltip-format = '' Homosexuality Statistics Homosexuality is online. Homosexuality levels are CRITICAL. ''; }; 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 blocked from going idle."; tooltip-format-deactivated = "System allowed 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" ]; } ]; }; }