From 698c953c55a8d21d51272d876b163978dad69e11 Mon Sep 17 00:00:00 2001 From: Mikhail Kilin Date: Sun, 1 Mar 2026 17:53:49 +0300 Subject: [PATCH] Customize waybar with Catppuccin Latte theme and niri modules - Replace sway modules with niri/workspaces, niri/window, niri/language - Remove battery, backlight, mpd (desktop machine) - Add custom modules: GPU (nvidia-smi), weather (wttr.in), mail (Thunderbird) - Add disk, privacy modules - Catppuccin Latte colors with Mocha dark background - Wi-Fi signal level icon, bold 15px font - Scripts for GPU, weather, and mail monitoring Co-Authored-By: Claude Opus 4.6 --- bazzite/waybar/config.jsonc | 272 ++++++++------------ bazzite/waybar/scripts/gpu.sh | 9 + bazzite/waybar/scripts/mail.py | 45 ++++ bazzite/waybar/scripts/weather.sh | 8 + bazzite/waybar/style.css | 407 ++++++++++++------------------ 5 files changed, 332 insertions(+), 409 deletions(-) create mode 100644 bazzite/waybar/scripts/gpu.sh create mode 100644 bazzite/waybar/scripts/mail.py create mode 100644 bazzite/waybar/scripts/weather.sh diff --git a/bazzite/waybar/config.jsonc b/bazzite/waybar/config.jsonc index 6b3918c..bc19250 100644 --- a/bazzite/waybar/config.jsonc +++ b/bazzite/waybar/config.jsonc @@ -1,214 +1,160 @@ // -*- mode: jsonc -*- { - // "layer": "top", // Waybar at top layer - // "position": "bottom", // Waybar position (top|bottom|left|right) - "height": 30, // Waybar height (to be removed for auto height) - // "width": 1280, // Waybar width - "spacing": 4, // Gaps between modules (4px) - // Choose the order of the modules + "layer": "top", + "height": 32, + "spacing": 8, + "modules-left": [ - "sway/workspaces", - "sway/mode", - "sway/scratchpad" + "niri/workspaces" ], "modules-center": [ - "sway/window" + "niri/window" ], "modules-right": [ + "privacy", + "custom/mail", "idle_inhibitor", + "custom/weather", "pulseaudio", "network", "power-profiles-daemon", "cpu", "memory", "temperature", - "backlight", - "sway/language", - "battery", + "custom/gpu", + "disk", + "niri/language", "clock", "tray" ], - // Modules configuration - // "sway/workspaces": { - // "disable-scroll": true, - // "all-outputs": true, - // "warp-on-scroll": false, - // "format": "{name}: {icon}", - // "format-icons": { - // "1": "", - // "2": "", - // "3": "", - // "4": "", - // "5": "", - // "urgent": "", - // "focused": "", - // "default": "" - // } - // }, - "keyboard-state": { - "numlock": true, - "capslock": true, - "format": "{name} {icon}", - "format-icons": { - "locked": "", - "unlocked": "" - } + + "niri/workspaces": { + "format": "{index}" }, - "sway/mode": { - "format": "{}" + + "niri/window": { + "format": "{}", + "max-length": 50 }, - "sway/scratchpad": { - "format": "{icon} {count}", - "show-empty": false, - "format-icons": ["", ""], + + "niri/language": { + "format": "{short}" + }, + + "privacy": { + "icon-spacing": 4, + "icon-size": 16, + "transition-duration": 250, + "modules": [ + { "type": "screenshare" }, + { "type": "audio-in" }, + { "type": "audio-out" } + ] + }, + + "custom/mail": { + "format": "{}", + "return-type": "json", + "exec": "python3 ~/.config/waybar/scripts/mail.py", + "interval": 60, "tooltip": true, - "tooltip-format": "{app}: {title}" - }, - "mpd": { - "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ", - "format-disconnected": "Disconnected ", - "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ", - "unknown-tag": "N/A", - "interval": 5, - "consume-icons": { - "on": " " - }, - "random-icons": { - "off": " ", - "on": " " - }, - "repeat-icons": { - "on": " " - }, - "single-icons": { - "on": "1 " - }, - "state-icons": { - "paused": "", - "playing": "" - }, - "tooltip-format": "MPD (connected)", - "tooltip-format-disconnected": "MPD (disconnected)" + "on-click": "flatpak run org.mozilla.Thunderbird" }, + "idle_inhibitor": { "format": "{icon}", "format-icons": { - "activated": "", - "deactivated": "" + "activated": "", + "deactivated": "" } }, - "tray": { - // "icon-size": 21, - "spacing": 10, - // "icons": { - // "blueman": "bluetooth", - // "TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png" - // } + + "custom/weather": { + "format": "{}", + "return-type": "json", + "exec": "~/.config/waybar/scripts/weather.sh", + "interval": 900, + "tooltip": true }, + + "custom/gpu": { + "format": " {}", + "return-type": "json", + "exec": "~/.config/waybar/scripts/gpu.sh", + "interval": 5, + "tooltip": true + }, + + "disk": { + "format": "{percentage_used}% ", + "path": "/", + "tooltip-format": "{used} / {total} ({percentage_used}%)" + }, + + "tray": { + "spacing": 10 + }, + "clock": { - // "timezone": "America/New_York", "tooltip-format": "{:%Y %B}\n{calendar}", + "format": "{:%H:%M}", "format-alt": "{:%Y-%m-%d}" }, + "cpu": { - "format": "{usage}% ", + "format": "{usage}% ", "tooltip": false }, + "memory": { - "format": "{}% " + "format": "{}% " }, + "temperature": { - // "thermal-zone": 2, - // "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input", "critical-threshold": 80, - // "format-critical": "{temperatureC}°C {icon}", "format": "{temperatureC}°C {icon}", - "format-icons": ["", "", ""] - }, - "backlight": { - // "device": "acpi_video1", - "format": "{percent}% {icon}", - "format-icons": ["🌑", "🌘", "🌗", "🌖", "🌕"] - }, - "battery": { - "states": { - // "good": 95, - "warning": 30, - "critical": 15 - }, - "format": "{capacity}% {icon}", - "format-full": "{capacity}% {icon}", - "format-charging": "{capacity}% ", - "format-plugged": "{capacity}% ", - "format-alt": "{time} {icon}", - // "format-good": "", // An empty format will hide the module - // "format-full": "", - "format-icons": ["", "", "", "", ""] - }, - "battery#bat2": { - "bat": "BAT2" - }, - "power-profiles-daemon": { - "format": "{icon}", - "tooltip-format": "Power profile: {profile}\nDriver: {driver}", - "tooltip": true, - "format-icons": { - "default": "", - "performance": "", - "balanced": "", - "power-saver": "" - } + "format-icons": ["", "", ""] }, + "network": { - // "interface": "wlp2*", // (Optional) To force the use of this interface - "format-wifi": "{essid} ({signalStrength}%) ", - "format-ethernet": "{ipaddr}/{cidr} ", - "tooltip-format": "{ifname} via {gwaddr} ", - "format-linked": "{ifname} (No IP) ", - "format-disconnected": "Disconnected ⚠", - "format-alt": "{ifname}: {ipaddr}/{cidr}" + "format-wifi": "{icon}", + "format-ethernet": "", + "format-icons": ["󰤯", "󰤟", "󰤢", "󰤥", "󰤨"], + "tooltip-format-wifi": "{essid} ({signalStrength}%)\n{ipaddr}/{cidr}", + "tooltip-format-ethernet": "{ifname}\n{ipaddr}/{cidr}", + "format-linked": "{ifname} (No IP) ", + "format-disconnected": "󰤭", + "on-click": "" }, + "pulseaudio": { - // "scroll-step": 1, // %, can be a float "format": "{volume}% {icon} {format_source}", - "format-bluetooth": "{volume}% {icon} {format_source}", - "format-bluetooth-muted": " {icon} {format_source}", - "format-muted": " {format_source}", - "format-source": "{volume}% ", - "format-source-muted": "", + "format-bluetooth": "{volume}% {icon} {format_source}", + "format-bluetooth-muted": " {icon} {format_source}", + "format-muted": " {format_source}", + "format-source": "{volume}% ", + "format-source-muted": "", "format-icons": { - "headphone": "", - "hands-free": "", - "headset": "", - "phone": "", - "portable": "", - "car": "", - "default": ["", "", ""] + "headphone": "", + "hands-free": "", + "headset": "", + "phone": "", + "portable": "", + "car": "", + "default": ["", "", ""] }, "on-click": "pavucontrol" }, - "custom/media": { - "format": "{icon} {text}", - "return-type": "json", - "max-length": 40, + + "power-profiles-daemon": { + "format": "{icon}", + "tooltip-format": "Power profile: {profile}\nDriver: {driver}", + "tooltip": true, "format-icons": { - "spotify": "", - "default": "🎜" - }, - "escape": true, - "exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder - // "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name - }, - "custom/power": { - "format" : "⏻ ", - "tooltip": false, - "menu": "on-click", - "menu-file": "$HOME/.config/waybar/power_menu.xml", // Menu file in resources folder - "menu-actions": { - "shutdown": "shutdown", - "reboot": "reboot", - "suspend": "systemctl suspend", - "hibernate": "systemctl hibernate" - } + "default": "", + "performance": "", + "balanced": "", + "power-saver": "" + } } } diff --git a/bazzite/waybar/scripts/gpu.sh b/bazzite/waybar/scripts/gpu.sh new file mode 100644 index 0000000..420f528 --- /dev/null +++ b/bazzite/waybar/scripts/gpu.sh @@ -0,0 +1,9 @@ +#!/bin/bash +data=$(nvidia-smi --query-gpu=utilization.gpu,temperature.gpu --format=csv,noheader,nounits 2>/dev/null) +if [ -z "$data" ]; then + echo '{"text": "N/A", "tooltip": "nvidia-smi not available"}' + exit 0 +fi +usage=$(echo "$data" | cut -d',' -f1 | tr -d ' ') +temp=$(echo "$data" | cut -d',' -f2 | tr -d ' ') +echo "{\"text\": \"${usage}% ${temp}°C\", \"tooltip\": \"GPU: ${usage}%\\nTemp: ${temp}°C\", \"class\": \"\"}" diff --git a/bazzite/waybar/scripts/mail.py b/bazzite/waybar/scripts/mail.py new file mode 100644 index 0000000..d249345 --- /dev/null +++ b/bazzite/waybar/scripts/mail.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +import glob +import json +import sqlite3 +import os + +profile_dirs = glob.glob( + os.path.expanduser("~/.var/app/org.mozilla.Thunderbird/.thunderbird/*.default-esr") +) or glob.glob( + os.path.expanduser("~/.thunderbird/*.default-esr") +) + +if not profile_dirs: + print(json.dumps({"text": "", "tooltip": "No Thunderbird profile found"})) + exit() + +db_path = os.path.join(profile_dirs[0], "global-messages-db.sqlite") +if not os.path.exists(db_path): + print(json.dumps({"text": "", "tooltip": "No message database"})) + exit() + +try: + conn = sqlite3.connect(f"file:{db_path}?mode=ro&nolock=1", uri=True) + cursor = conn.cursor() + cursor.execute( + "SELECT COUNT(*) FROM messages WHERE jsonAttributes LIKE '%\"read\":false%' OR jsonAttributes LIKE '%\"read\": false%'" + ) + count = cursor.fetchone()[0] + conn.close() +except Exception: + # fallback: try folderStatus + try: + conn = sqlite3.connect(f"file:{db_path}?mode=ro&nolock=1", uri=True) + cursor = conn.cursor() + cursor.execute("SELECT SUM(numNewMessages) FROM folderlocations WHERE numNewMessages > 0") + row = cursor.fetchone() + count = row[0] if row and row[0] else 0 + conn.close() + except Exception: + count = 0 + +if count > 0: + print(json.dumps({"text": f"󰇮 {count}", "tooltip": f"{count} unread emails", "class": "unread"})) +else: + print(json.dumps({"text": "󰇮", "tooltip": "No unread emails"})) diff --git a/bazzite/waybar/scripts/weather.sh b/bazzite/waybar/scripts/weather.sh new file mode 100644 index 0000000..e37ecd7 --- /dev/null +++ b/bazzite/waybar/scripts/weather.sh @@ -0,0 +1,8 @@ +#!/bin/bash +weather=$(curl -s "wttr.in/?format=%c%t" 2>/dev/null) +if [ -z "$weather" ] || echo "$weather" | grep -q "Unknown"; then + echo '{"text": "N/A", "tooltip": "Weather unavailable"}' + exit 0 +fi +tooltip=$(curl -s "wttr.in/?format=%l:+%C+%t+%w+%h" 2>/dev/null) +echo "{\"text\": \"${weather}\", \"tooltip\": \"${tooltip}\"}" diff --git a/bazzite/waybar/style.css b/bazzite/waybar/style.css index e031037..18ba1a2 100644 --- a/bazzite/waybar/style.css +++ b/bazzite/waybar/style.css @@ -1,12 +1,40 @@ +/* Catppuccin Latte */ +@define-color rosewater #dc8a78; +@define-color flamingo #dd7878; +@define-color pink #ea76cb; +@define-color mauve #8839ef; +@define-color red #d20f39; +@define-color maroon #e64553; +@define-color peach #fe640b; +@define-color yellow #df8e1d; +@define-color green #40a02b; +@define-color teal #179299; +@define-color sky #04a5e5; +@define-color sapphire #209fb5; +@define-color blue #1e66f5; +@define-color lavender #7287fd; +@define-color text #4c4f69; +@define-color subtext1 #5c5f77; +@define-color subtext0 #6c6f85; +@define-color overlay2 #7c7f93; +@define-color overlay1 #8c8fa1; +@define-color overlay0 #9ca0b0; +@define-color surface2 #acb0be; +@define-color surface1 #bcc0cc; +@define-color surface0 #ccd0da; +@define-color base #eff1f5; +@define-color mantle #e6e9ef; +@define-color crust #dce0e8; + * { - font-family: 'Noto Sans Mono', 'Font Awesome 6 Free', 'Font Awesome 6 Brands', monospace; - font-size: 13px; + font-family: 'JetBrainsMono Nerd Font', 'Noto Sans Mono', monospace; + font-size: 15px; + font-weight: bold; } window#waybar { - background-color: rgba(43, 48, 59, 0.5); - border-bottom: 3px solid rgba(100, 114, 125, 0.5); - color: #ffffff; + background-color: #1e1e2e; + color: @text; transition-property: background-color; transition-duration: .5s; } @@ -15,86 +43,56 @@ window#waybar.hidden { opacity: 0.2; } -/* -window#waybar.empty { - background-color: transparent; -} -window#waybar.solo { - background-color: #FFFFFF; -} -*/ - -window#waybar.termite { - background-color: #3F3F3F; -} - -window#waybar.chromium { - background-color: #000000; - border: none; -} - button { - /* Use box-shadow instead of border so the text isn't offset */ box-shadow: inset 0 -3px transparent; - /* Avoid rounded borders under each button name */ border: none; border-radius: 0; } -/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ button:hover { background: inherit; - box-shadow: inset 0 -3px #ffffff; -} - -/* you can set a style on hover for any module like this */ -#pulseaudio:hover { - background-color: #a37800; + box-shadow: inset 0 -2px @blue; } +/* Workspaces */ #workspaces button { - padding: 0 5px; + padding: 0 6px; background-color: transparent; - color: #ffffff; + color: #a6adc8; } #workspaces button:hover { - background: rgba(0, 0, 0, 0.2); + background: alpha(@surface0, 0.5); } -#workspaces button.focused { - background-color: #64727D; - box-shadow: inset 0 -3px #ffffff; +#workspaces button.active { + color: #cdd6f4; + box-shadow: inset 0 -2px @blue; } #workspaces button.urgent { - background-color: #eb4d4b; -} - -#mode { - background-color: #64727D; - box-shadow: inset 0 -3px #ffffff; + background-color: alpha(@red, 0.2); + color: @red; } +/* Common module styling */ #clock, -#battery, #cpu, #memory, #disk, #temperature, -#backlight, #network, #pulseaudio, #wireplumber, -#custom-media, #tray, -#mode, #idle_inhibitor, -#scratchpad, #power-profiles-daemon, -#mpd { +#language, +#custom-gpu, +#custom-weather, +#privacy { padding: 0 10px; - color: #ffffff; + color: @text; } #window, @@ -102,139 +100,135 @@ button:hover { margin: 0 4px; } -/* If workspaces is the leftmost module, omit left margin */ -.modules-left > widget:first-child > #workspaces { - margin-left: 0; -} - -/* If workspaces is the rightmost module, omit right margin */ -.modules-right > widget:last-child > #workspaces { - margin-right: 0; +#window { + color: #cdd6f4; } +/* Clock */ #clock { - background-color: #64727D; -} - -#battery { - background-color: #ffffff; - color: #000000; -} - -#battery.charging, #battery.plugged { - color: #ffffff; - background-color: #26A65B; -} - -@keyframes blink { - to { - background-color: #ffffff; - color: #000000; - } -} - -/* Using steps() instead of linear as a timing function to limit cpu usage */ -#battery.critical:not(.charging) { - background-color: #f53c3c; - color: #ffffff; - animation-name: blink; - animation-duration: 0.5s; - animation-timing-function: steps(12); - animation-iteration-count: infinite; - animation-direction: alternate; -} - -#power-profiles-daemon { - padding-right: 15px; -} - -#power-profiles-daemon.performance { - background-color: #f53c3c; - color: #ffffff; -} - -#power-profiles-daemon.balanced { - background-color: #2980b9; - color: #ffffff; -} - -#power-profiles-daemon.power-saver { - background-color: #2ecc71; - color: #000000; -} - -label:focus { - background-color: #000000; + color: @blue; + font-weight: bold; } +/* CPU */ #cpu { - background-color: #2ecc71; - color: #000000; + color: @green; } +/* Memory */ #memory { - background-color: #9b59b6; -} - -#disk { - background-color: #964B00; -} - -#backlight { - background-color: #90b1b1; -} - -#network { - background-color: #2980b9; -} - -#network.disconnected { - background-color: #f53c3c; -} - -#pulseaudio { - background-color: #f1c40f; - color: #000000; -} - -#pulseaudio.muted { - background-color: #90b1b1; - color: #2a5c45; -} - -#wireplumber { - background-color: #fff0f5; - color: #000000; -} - -#wireplumber.muted { - background-color: #f53c3c; -} - -#custom-media { - background-color: #66cc99; - color: #2a5c45; - min-width: 100px; -} - -#custom-media.custom-spotify { - background-color: #66cc99; -} - -#custom-media.custom-vlc { - background-color: #ffa000; + color: @mauve; } +/* Temperature */ #temperature { - background-color: #f0932b; + color: @peach; } #temperature.critical { - background-color: #eb4d4b; + color: @red; } +/* Network */ +#network { + color: @sapphire; + font-size: 20px; +} + +#network.disconnected { + color: @red; +} + +/* Pulseaudio */ +#pulseaudio { + color: @yellow; +} + +#pulseaudio.muted { + color: @overlay1; +} + +/* Language */ +#language { + color: @teal; + font-weight: bold; +} + +/* Idle inhibitor */ +#idle_inhibitor { + color: @overlay1; +} + +#idle_inhibitor.activated { + color: @peach; +} + +/* Power profiles */ +#power-profiles-daemon { + color: @subtext0; +} + +#power-profiles-daemon.performance { + color: @red; +} + +#power-profiles-daemon.balanced { + color: @blue; +} + +#power-profiles-daemon.power-saver { + color: @green; +} + +/* Mail */ +#custom-mail { + color: @lavender; +} + +#custom-mail.unread { + color: @red; +} + +/* Disk */ +#disk { + color: @flamingo; +} + +/* GPU */ +#custom-gpu { + color: @green; +} + +/* Weather */ +#custom-weather { + color: @sky; +} + +/* Privacy */ +#privacy { + padding: 0; +} + +#privacy-item { + padding: 0 5px; + color: @base; +} + +#privacy-item.screenshare { + background-color: @peach; +} + +#privacy-item.audio-in { + background-color: @red; +} + +#privacy-item.audio-out { + background-color: @blue; +} + +/* Tray */ #tray { - background-color: #2980b9; + color: @text; } #tray > .passive { @@ -243,84 +237,5 @@ label:focus { #tray > .needs-attention { -gtk-icon-effect: highlight; - background-color: #eb4d4b; -} - -#idle_inhibitor { - background-color: #2d3436; -} - -#idle_inhibitor.activated { - background-color: #ecf0f1; - color: #2d3436; -} - -#mpd { - background-color: #66cc99; - color: #2a5c45; -} - -#mpd.disconnected { - background-color: #f53c3c; -} - -#mpd.stopped { - background-color: #90b1b1; -} - -#mpd.paused { - background-color: #51a37a; -} - -#language { - background: #00b093; - color: #740864; - padding: 0 5px; - margin: 0 5px; - min-width: 16px; -} - -#keyboard-state { - background: #97e1ad; - color: #000000; - padding: 0 0px; - margin: 0 5px; - min-width: 16px; -} - -#keyboard-state > label { - padding: 0 5px; -} - -#keyboard-state > label.locked { - background: rgba(0, 0, 0, 0.2); -} - -#scratchpad { - background: rgba(0, 0, 0, 0.2); -} - -#scratchpad.empty { - background-color: transparent; -} - -#privacy { - padding: 0; -} - -#privacy-item { - padding: 0 5px; - color: white; -} - -#privacy-item.screenshare { - background-color: #cf5700; -} - -#privacy-item.audio-in { - background-color: #1ca000; -} - -#privacy-item.audio-out { - background-color: #0069d4; + color: @red; }