more tidying, fixed waybar, added comfyui to eva-03

This commit is contained in:
2026-03-10 02:35:30 -07:00
parent 2a8c1c85fc
commit 5445adec96
28 changed files with 8890 additions and 109 deletions

View File

@@ -134,6 +134,7 @@
modules = [
./hosts/eva-03/configuration.nix
inputs.home-manager.nixosModules.default
comfyui-nix.nixosModules.default
];
};

View File

@@ -0,0 +1,376 @@
{
pkgs,
config,
lib,
...
}: let
terminal = "kitty";
base00 = "0F1419";
base01 = "131721";
base03 = "3E4B59";
base05 = "E6E1CF";
base06 = "E6E1CF";
base07 = "F3F4F5";
base08 = "F07178";
base09 = "FF8F40";
base0A = "FFB454";
base0B = "B8CC52";
base0C = "95E6CB";
base0D = "59C2FF";
base0E = "D2A6FF";
base0F = "E6B673";
scriptsDir = ./scripts;
scripts = builtins.attrNames (builtins.readDir scriptsDir);
in
with lib; {
home.file = builtins.listToAttrs (map
(name: {
name = ".config/waybar/scripts/" + name;
value = {
source = "${scriptsDir}/${name}";
executable = true;
};
})
scripts);
# Configure & Theme Waybar
programs.waybar = {
enable = true;
package = pkgs.waybar;
settings = [
{
layer = "top";
position = "top";
modules-center = ["network" "pulseaudio" "cpu" "hyprland/workspaces" "memory" "disk" "clock" "custom/weather"];
modules-left = ["custom/startmenu" "hyprland/window"]; # Eternal: [ "hyprland/workspaces" "cpu" "memory" "network" ]
modules-right = ["tray" "idle_inhibitor" "custom/notification" "battery" "custom/power"]; # Eternal: [ "idle_inhibitor" "pulseaudio" "clock" "custom/notification" "tray" ]
"hyprland/workspaces" = {
format = "{name}";
format-icons = {
default = " ";
active = " ";
urgent = " ";
};
on-scroll-up = "hyprctl dispatch workspace e+1";
on-scroll-down = "hyprctl dispatch workspace e-1";
};
"clock" = {
format = '' {:%H:%M}'';
/*
''{: %I:%M %p}'';
*/
tooltip = true;
tooltip-format = "<big>{:%A, %d.%B %Y }</big><tt><small>{calendar}</small></tt>";
};
"hyprland/window" = {
max-length = 60;
separate-outputs = false;
};
"memory" = {
interval = 5;
format = " {}%";
tooltip = true;
on-click = "${terminal} -e btop";
};
"cpu" = {
interval = 5;
format = " {usage:2}%";
tooltip = true;
on-click = "${terminal} -e btop";
};
"disk" = {
format = " {free}";
tooltip = true;
on-click = "${terminal} -e sh -c df -h ; read";
};
"network" = {
format-icons = ["󰤯" "󰤟" "󰤢" "󰤥" "󰤨"];
format-ethernet = " {bandwidthDownBits}";
format-wifi = " {bandwidthDownBits}";
format-disconnected = "󰤮";
tooltip = false;
on-click = "${terminal} -e btop";
};
"tray" = {
spacing = 12;
};
"pulseaudio" = {
format = "{icon} {volume}% {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-icons = {
headphone = "";
hands-free = "";
headset = "";
phone = "";
portable = "";
car = "";
default = ["" "" ""];
};
on-click = "pavucontrol";
};
"custom/power" = {
tooltip = true;
"tooltip-format" = "Left Click: Power Menu (qs-wlogout)\nRight Click: Rofi Power Menu";
format = " ";
on-click = "qs-wlogout";
"on-click-right" = "~/.config/waybar/scripts/power-menu.sh";
};
"custom/startmenu" = {
tooltip = true;
"tooltip-format" = "App menu";
format = "";
# on-click = "rofi -show drun";
on-click = "launch-nwg-menu";
"on-click-right" = "nwg-drawer -mr 225 -ml 225 -mt 200 -mb 200 -is 48 --spacing 15";
};
"idle_inhibitor" = {
format = "{icon}";
format-icons = {
activated = " ";
deactivated = " ";
};
tooltip = "true";
};
"custom/notification" = {
tooltip = false;
format = "{icon} {}";
format-icons = {
notification = "<span foreground='red'><sup></sup></span>";
none = "";
dnd-notification = "<span foreground='red'><sup></sup></span>";
dnd-none = "";
inhibited-notification = "<span foreground='red'><sup></sup></span>";
inhibited-none = "";
dnd-inhibited-notification = "<span foreground='red'><sup></sup></span>";
dnd-inhibited-none = "";
};
return-type = "json";
exec-if = "which swaync-client";
exec = "swaync-client -swb";
on-click = "pavucontrol";
escape = true;
};
"custom/weather" = {
return-type = "json";
exec = "sh -lc 'WEATHER_ICON_STYLE=emoji WEATHER_TOOLTIP_MARKUP=1 ~/.local/bin/weather'";
interval = 600;
tooltip = true;
};
"battery" = {
states = {
warning = 30;
critical = 15;
};
format = "{icon} {capacity}%";
format-charging = "󰂄 {capacity}%";
format-plugged = "󱘖 {capacity}%";
format-icons = ["󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹"];
on-click = "";
tooltip = false;
};
}
];
style = concatStrings [
''
* {
font-size: 16px;
font-family: JetBrainsMono Nerd Font, Font Awesome, sans-serif;
font-weight: bold;
}
window#waybar {
/*
background-color: rgba(26,27,38,0);
border-bottom: 1px solid rgba(26,27,38,0);
border-radius: 0px;
color: #${base0F};
*/
background-color: rgba(26,27,38,0);
border-bottom: 1px solid rgba(26,27,38,0);
border-radius: 0px;
color: #${base0F};
}
#workspaces {
/*
Eternal
background: linear-gradient(180deg, #${base00}, #${base01});
margin: 5px 5px 5px 0px;
padding: 0px 10px;
border-radius: 0px 15px 50px 0px;
border: 0px;
font-style: normal;
color: #${base00};
*/
background: linear-gradient(45deg, #${base01}, #${base01});
margin: 5px;
padding: 0px 1px;
border-radius: 15px;
border: 0px;
font-style: normal;
color: #${base00};
}
#workspaces button {
padding: 0px 5px;
margin: 4px 3px;
border-radius: 15px;
border: 0px;
color: #${base00};
background: linear-gradient(45deg, #${base0D}, #${base0E});
opacity: 0.5;
transition: all 0.3s ease-in-out;
}
#workspaces button.active {
padding: 0px 5px;
margin: 4px 3px;
border-radius: 15px;
border: 0px;
color: #${base00};
background: linear-gradient(45deg, #${base0D}, #${base0E});
opacity: 1.0;
min-width: 40px;
transition: all 0.3s ease-in-out;
}
#workspaces button:hover {
border-radius: 15px;
color: #${base00};
background: linear-gradient(45deg, #${base0D}, #${base0E});
opacity: 0.8;
}
tooltip {
background: #${base00};
border: 1px solid #${base0E};
border-radius: 10px;
}
tooltip label {
color: #${base07};
}
#window {
/*
Eternal
color: #${base05};
background: #${base00};
border-radius: 15px;
margin: 5px;
padding: 2px 20px;
*/
margin: 5px;
padding: 2px 20px;
color: #${base05};
background: #${base01};
border-radius: 50px 15px 50px 15px;
}
#memory {
color: #${base0F};
/*
Eternal
background: #${base00};
border-radius: 50px 15px 50px 15px;
margin: 5px;
padding: 2px 20px;
*/
background: #${base01};
margin: 5px;
padding: 2px 20px;
border-radius: 15px 50px 15px 50px;
}
#clock {
color: #${base0B};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#idle_inhibitor {
color: #${base0A};
background: #${base00};
border-radius: 50px 15px 50px 15px;
margin: 5px;
padding: 2px 20px;
}
#cpu {
color: #${base07};
background: #${base00};
border-radius: 50px 15px 50px 15px;
margin: 5px;
padding: 2px 20px;
}
#disk {
color: #${base03};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#battery {
color: #${base08};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#network {
color: #${base09};
background: #${base00};
border-radius: 50px 15px 50px 15px;
margin: 5px;
padding: 2px 20px;
}
#tray {
color: #${base05};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#pulseaudio {
color: #${base0D};
/*
Eternal
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
*/
background: #${base01};
margin: 4px;
padding: 2px 20px;
border-radius: 50px 15px 50px 15px;
}
#custom-notification {
color: #${base0C};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#custom-startmenu {
color: #${base03};
background: #${base00};
border-radius: 0px 15px 50px 0px;
margin: 5px 5px 5px 0px;
padding: 2px 20px;
}
#idle_inhibitor {
color: #${base09};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#custom-power {
color: #${base08}; /* Red color for power menu */
background: #${base00};
border-radius: 15px 0px 0px 50px;
margin: 5px 0px 5px 5px;
padding: 2px 20px;
}
''
];
};
}

View File

@@ -0,0 +1,719 @@
#!/usr/bin/env python3
# /* ---- 💫 https://github.com/JaKooLit 💫 ---- */ #
# Rewritten to use Open-Meteo APIs (worldwide, no API key) for robust weather data.
# Outputs Waybar-compatible JSON and a simple text cache.
import json
import os
import sys
import time
import html
import subprocess
from typing import Any, Dict, List, Optional, Tuple
from datetime import datetime
import requests
# =============== Configuration ===============
# You can configure behavior via environment variables OR the constants below.
# Examples (zsh):
# # One-off run
# # WEATHER_UNITS can be "metric" or "imperial"
# WEATHER_UNITS=imperial WEATHER_PLACE="Concord, NH" python3 ~/.config/waybar/scripts/Weather.py
#
# # Persist in current shell session
# export WEATHER_UNITS=imperial
# export WEATHER_LAT=43.2229
# export WEATHER_LON=-71.332
# export WEATHER_PLACE="Concord, NH"
# export WEATHER_TOOLTIP_MARKUP=1 # 1 to enable Pango markup, 0 to disable
# export WEATHER_LOC_ICON="📍" # or "*" for ASCII-only
#
CACHE_DIR = os.path.expanduser("~/.cache")
API_CACHE_PATH = os.path.join(CACHE_DIR, "open_meteo_cache.json")
SIMPLE_TEXT_CACHE_PATH = os.path.join(CACHE_DIR, ".weather_cache")
CACHE_TTL_SECONDS = int(os.getenv("WEATHER_CACHE_TTL", "600")) # default 10 minutes
# Units: metric or imperial (default imperial for ddubsos branch)
UNITS = os.getenv("WEATHER_UNITS", "imperial").strip().lower() # metric|imperial
# Optional manual coordinates
ENV_LAT = os.getenv("WEATHER_LAT")
ENV_LON = os.getenv("WEATHER_LON")
# Optional manual place override for tooltip
ENV_PLACE = os.getenv("WEATHER_PLACE")
# Manual place name set inside this file. If set (non-empty), this takes top priority.
# Example: MANUAL_PLACE = "Concord, NH, US"
MANUAL_PLACE: Optional[str] = None
# Location icon in tooltip (default to a standard emoji to avoid missing glyphs)
LOC_ICON = os.getenv("WEATHER_LOC_ICON", "📍")
# Enable/disable Pango markup in tooltip (1/0, true/false)
TOOLTIP_MARKUP = os.getenv("WEATHER_TOOLTIP_MARKUP", "1").lower() not in ("0", "false", "no")
# Optional debug logging to stderr (set WEATHER_DEBUG=1 to enable)
DEBUG = os.getenv("WEATHER_DEBUG", "0").lower() not in ("0", "false", "no")
# HTTP settings
UA = (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/128.0 Safari/537.36"
)
TIMEOUT = 8
SESSION = requests.Session()
SESSION.headers.update({"User-Agent": UA})
# =============== Icon and status mapping ===============
# Icon style: 'emoji' (colorful), 'nerd' (mono glyphs), or 'gnome' (symbolic names, useful for other bars)
ICON_STYLE = os.getenv("WEATHER_ICON_STYLE", "emoji").strip().lower() # emoji|nerd|gnome
# Nerd Font weather glyphs (mono)
WEATHER_ICONS_NERD = {
"sunnyDay": "󰖙",
"clearNight": "󰖔",
"cloudyFoggyDay": "",
"cloudyFoggyNight": "",
"rainyDay": "",
"rainyNight": "",
"snowyIcyDay": "",
"snowyIcyNight": "",
"severe": "",
"default": "",
}
# Colored emoji set (renders with system emoji font, gives the "3D" look)
WEATHER_ICONS_EMOJI = {
"sunnyDay": "☀️",
"clearNight": "🌙",
"cloudyFoggyDay": "⛅️",
"cloudyFoggyNight": "☁️🌙",
"rainyDay": "🌦️",
"rainyNight": "🌧️",
"snowyIcyDay": "🌨️",
"snowyIcyNight": "❄️🌙",
"severe": "⛈️",
"default": "🌥️",
}
# GNOME symbolic icon names (not used directly by Waybar, here for parity)
WEATHER_ICONS_GNOME = {
"sunnyDay": "Sun-symbolic",
"clearNight": "Moon-symbolic",
"cloudyFoggyDay": "CloudSun-symbolic",
"cloudyFoggyNight": "CloudyMoon-symbolic",
"rainyDay": "CloudRain-symbolic",
"rainyNight": "CloudRain-symbolic",
"snowyIcyDay": "CloudSnowfall-symbolic",
"snowyIcyNight": "CloudSnowfall-symbolic",
"severe": "CloudBolt-symbolic",
"default": "Cloud-symbolic",
}
# Optional: allow disabling automatic fallback via env
DISABLE_EMOJI_FALLBACK = os.getenv("WEATHER_DISABLE_EMOJI_FALLBACK", "0").lower() in ("1", "true", "yes")
# Basic detection of color emoji font availability via fontconfig (fc-list)
EMOJI_FONT_CANDIDATES = [
"Noto Color Emoji",
"Noto Emoji",
"Apple Color Emoji",
"Segoe UI Emoji",
"Twitter Color Emoji",
"Twemoji",
"JoyPixels",
]
def has_color_emoji_font() -> bool:
try:
# Quick check: list installed fonts; search for any candidate name
proc = subprocess.run(
["fc-list"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
timeout=1.5,
check=False,
)
out = proc.stdout or ""
out_lower = out.lower()
for name in EMOJI_FONT_CANDIDATES:
if name.lower() in out_lower:
return True
except Exception:
# If detection fails, don't block emoji; assume not available to be safe on minimal setups
return False
return False
# Determine the active icon style with fallback from emoji -> nerd when no color emoji fonts are present
if ICON_STYLE == "emoji" and not DISABLE_EMOJI_FALLBACK and not has_color_emoji_font():
ACTIVE_ICON_STYLE = "nerd"
else:
ACTIVE_ICON_STYLE = ICON_STYLE
# Choose active set
WEATHER_ICONS = (
WEATHER_ICONS_EMOJI if ACTIVE_ICON_STYLE == "emoji" else WEATHER_ICONS_NERD if ACTIVE_ICON_STYLE == "nerd" else WEATHER_ICONS_GNOME
)
WMO_STATUS = {
0: "Clear sky",
1: "Mainly clear",
2: "Partly cloudy",
3: "Overcast",
45: "Fog",
48: "Depositing rime fog",
51: "Light drizzle",
53: "Moderate drizzle",
55: "Dense drizzle",
56: "Freezing drizzle",
57: "Freezing drizzle",
61: "Light rain",
63: "Moderate rain",
65: "Heavy rain",
66: "Freezing rain",
67: "Freezing rain",
71: "Slight snow",
73: "Moderate snow",
75: "Heavy snow",
77: "Snow grains",
80: "Rain showers",
81: "Rain showers",
82: "Violent rain showers",
85: "Snow showers",
86: "Heavy snow showers",
95: "Thunderstorm",
96: "Thunderstorm w/ hail",
99: "Thunderstorm w/ hail",
}
def wmo_to_icon(code: int, is_day: int) -> str:
day = bool(is_day)
if code == 0:
return WEATHER_ICONS["sunnyDay" if day else "clearNight"]
if code in (1, 2, 3, 45, 48):
return WEATHER_ICONS["cloudyFoggyDay" if day else "cloudyFoggyNight"]
if code in (51, 53, 55, 61, 63, 65, 80, 81, 82):
return WEATHER_ICONS["rainyDay" if day else "rainyNight"]
if code in (56, 57, 66, 67, 71, 73, 75, 77, 85, 86):
return WEATHER_ICONS["snowyIcyDay" if day else "snowyIcyNight"]
if code in (95, 96, 99):
return WEATHER_ICONS["severe"]
return WEATHER_ICONS["default"]
def wmo_to_status(code: int) -> str:
return WMO_STATUS.get(code, "Unknown")
# =============== Utilities ===============
def esc(s: Optional[str]) -> str:
return html.escape(s, quote=False) if s else ""
def log_debug(msg: str) -> None:
if DEBUG:
print(msg, file=sys.stderr)
def temp_color(temp_val: Optional[float]) -> str:
"""Choose a color for the temperature number based on value and unit set."""
if not isinstance(temp_val, (int, float)):
return "#cdd6f4" # default text
# Work in Fahrenheit for thresholds if imperial, else Celsius
t = float(temp_val)
if UNITS == "metric":
if t <= -5:
return "#89dceb" # sky
if t <= 5:
return "#74c7ec" # sapphire
if t <= 15:
return "#a6e3a1" # green
if t <= 25:
return "#f9e2af" # yellow
if t <= 32:
return "#fab387" # peach
return "#f38ba8" # red
else:
if t <= 25:
return "#89dceb"
if t <= 41:
return "#74c7ec"
if t <= 60:
return "#a6e3a1"
if t <= 77:
return "#f9e2af"
if t <= 90:
return "#fab387"
return "#f38ba8"
def ensure_cache_dir() -> None:
try:
os.makedirs(CACHE_DIR, exist_ok=True)
except Exception as e:
print(f"Error creating cache dir: {e}", file=sys.stderr)
def read_api_cache() -> Optional[Dict[str, Any]]:
try:
if not os.path.exists(API_CACHE_PATH):
return None
with open(API_CACHE_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
if (time.time() - data.get("timestamp", 0)) <= CACHE_TTL_SECONDS:
return data
return None
except Exception as e:
print(f"Error reading cache: {e}", file=sys.stderr)
return None
def write_api_cache(payload: Dict[str, Any]) -> None:
try:
ensure_cache_dir()
payload["timestamp"] = time.time()
with open(API_CACHE_PATH, "w", encoding="utf-8") as f:
json.dump(payload, f)
except Exception as e:
print(f"Error writing API cache: {e}", file=sys.stderr)
def write_simple_text_cache(text: str) -> None:
try:
ensure_cache_dir()
with open(SIMPLE_TEXT_CACHE_PATH, "w", encoding="utf-8") as f:
f.write(text)
except Exception as e:
print(f"Error writing simple cache: {e}", file=sys.stderr)
def get_coords() -> Tuple[float, float]:
# 1) Explicit env
if ENV_LAT and ENV_LON:
try:
return float(ENV_LAT), float(ENV_LON)
except ValueError:
print("Invalid WEATHER_LAT/WEATHER_LON; falling back to IP geolocation", file=sys.stderr)
# 2) Try cached coordinates from last successful forecast
try:
cached = read_api_cache()
if cached and isinstance(cached, dict):
fc = cached.get("forecast") or {}
lat = fc.get("latitude")
lon = fc.get("longitude")
if isinstance(lat, (int, float)) and isinstance(lon, (int, float)):
return float(lat), float(lon)
except Exception as e:
print(f"Reading cached coords failed: {e}", file=sys.stderr)
# 3) IP-based geolocation with multiple providers (prefer ipwho.is, ipapi.co; ipinfo.io as fallback)
# ipwho.is
try:
resp = SESSION.get("https://ipwho.is/", timeout=TIMEOUT)
resp.raise_for_status()
data = resp.json()
if data.get("success"):
lat = data.get("latitude")
lon = data.get("longitude")
if isinstance(lat, (int, float)) and isinstance(lon, (int, float)):
return float(lat), float(lon)
except Exception as e:
print(f"ipwho.is failed: {e}", file=sys.stderr)
# ipapi.co
try:
resp = SESSION.get("https://ipapi.co/json", timeout=TIMEOUT)
resp.raise_for_status()
data = resp.json()
lat = data.get("latitude")
lon = data.get("longitude")
if isinstance(lat, (int, float)) and isinstance(lon, (int, float)):
return float(lat), float(lon)
except Exception as e:
print(f"ipapi.co failed: {e}", file=sys.stderr)
# ipinfo.io (fallback)
try:
resp = SESSION.get("https://ipinfo.io/json", timeout=TIMEOUT)
resp.raise_for_status()
data = resp.json()
loc = data.get("loc")
if loc and "," in loc:
lat_s, lon_s = loc.split(",", 1)
return float(lat_s), float(lon_s)
except Exception as e:
print(f"ipinfo.io failed: {e}", file=sys.stderr)
# 4) Last resort
print("IP geolocation failed: no providers succeeded", file=sys.stderr)
return 0.0, 0.0
def units_params(units: str) -> Dict[str, str]:
if units == "imperial":
return {
"temperature_unit": "fahrenheit",
"wind_speed_unit": "mph",
"precipitation_unit": "inch",
}
# default metric
return {
"temperature_unit": "celsius",
"wind_speed_unit": "kmh",
"precipitation_unit": "mm",
}
def format_visibility(meters: Optional[float]) -> str:
if meters is None:
return ""
try:
if UNITS == "imperial":
miles = meters / 1609.344
return f"{miles:.1f} mi"
else:
km = meters / 1000.0
return f"{km:.1f} km"
except Exception:
return ""
# =============== API Fetching ===============
def fetch_open_meteo(lat: float, lon: float) -> Dict[str, Any]:
base = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": lat,
"longitude": lon,
"current": "temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,wind_direction_10m,weather_code,visibility,precipitation,pressure_msl,is_day",
"hourly": "precipitation_probability,weather_code",
"daily": "temperature_2m_max,temperature_2m_min",
"timezone": "auto",
}
params.update(units_params(UNITS))
resp = SESSION.get(base, params=params, timeout=TIMEOUT)
resp.raise_for_status()
return resp.json()
def fetch_aqi(lat: float, lon: float) -> Optional[Dict[str, Any]]:
try:
base = "https://air-quality-api.open-meteo.com/v1/air-quality"
params = {
"latitude": lat,
"longitude": lon,
"current": "european_aqi",
"timezone": "auto",
}
resp = SESSION.get(base, params=params, timeout=TIMEOUT)
resp.raise_for_status()
return resp.json()
except Exception as e:
print(f"AQI fetch failed: {e}", file=sys.stderr)
return None
def fetch_place(lat: float, lon: float) -> Optional[str]:
"""Reverse geocode lat/lon to an approximate place. Tries Nominatim first, then Open-Meteo."""
lang = os.getenv("WEATHER_LANG", "en")
# 1) Nominatim (OpenStreetMap)
try:
base = "https://nominatim.openstreetmap.org/reverse"
params = {
"lat": lat,
"lon": lon,
"format": "jsonv2",
"accept-language": lang,
}
headers = {"User-Agent": UA + " Weather.py/1.0"}
resp = SESSION.get(base, params=params, headers=headers, timeout=TIMEOUT)
resp.raise_for_status()
data = resp.json()
address = data.get("address", {})
name = data.get("name") or address.get("city") or address.get("town") or address.get("village") or address.get("hamlet")
admin1 = address.get("state")
country = address.get("country")
parts = [part for part in [name, admin1, country] if part]
if parts:
return ", ".join(parts)
except Exception as e:
log_debug(f"Reverse geocoding (Nominatim) failed: {e}")
# 2) Open-Meteo reverse (fallback)
try:
base = "https://geocoding-api.open-meteo.com/v1/reverse"
params = {
"latitude": lat,
"longitude": lon,
"language": lang,
"format": "json",
}
resp = SESSION.get(base, params=params, timeout=TIMEOUT)
resp.raise_for_status()
data = resp.json()
results = data.get("results") or []
if results:
p = results[0]
name = p.get("name")
admin1 = p.get("admin1")
country = p.get("country")
parts = [part for part in [name, admin1, country] if part]
if parts:
return ", ".join(parts)
except Exception as e:
log_debug(f"Reverse geocoding (Open-Meteo) failed: {e}")
return None
# =============== Build Output ===============
def safe_get(dct: Dict[str, Any], *keys, default=None):
cur: Any = dct
for k in keys:
if isinstance(cur, dict):
if k not in cur:
return default
cur = cur[k]
elif isinstance(cur, list):
try:
cur = cur[k] # type: ignore[index]
except Exception:
return default
else:
return default
return cur
def build_hourly_precip(forecast: Dict[str, Any]) -> str:
try:
times: List[str] = safe_get(forecast, "hourly", "time", default=[]) or []
probs: List[Optional[float]] = safe_get(
forecast, "hourly", "precipitation_probability", default=[]
) or []
cur_time: Optional[str] = safe_get(forecast, "current", "time")
idx = times.index(cur_time) if cur_time in times else 0
window = probs[idx : idx + 6]
if not window:
return ""
parts = [f"{int(p)}%" if p is not None else "-" for p in window]
return " (next 6h) " + " ".join(parts)
except Exception:
return ""
def build_output(lat: float, lon: float, forecast: Dict[str, Any], aqi: Optional[Dict[str, Any]], place: Optional[str] = None) -> Tuple[Dict[str, Any], str]:
cur = forecast.get("current", {})
cur_units = forecast.get("current_units", {})
daily = forecast.get("daily", {})
daily_units = forecast.get("daily_units", {})
temp_val = cur.get("temperature_2m")
temp_unit = cur_units.get("temperature_2m", "")
temp_str = f"{int(round(temp_val))}{temp_unit}" if isinstance(temp_val, (int, float)) else "N/A"
temp_col = temp_color(temp_val)
feels_val = cur.get("apparent_temperature")
feels_unit = cur_units.get("apparent_temperature", "")
feels_str = f"Feels like {int(round(feels_val))}{feels_unit}" if isinstance(feels_val, (int, float)) else ""
is_day = int(cur.get("is_day", 1) or 1)
code = int(cur.get("weather_code", -1) or -1)
unavailable = False
# Fallbacks: if current weather_code is missing/invalid
if code == -1:
try:
times: List[str] = safe_get(forecast, "hourly", "time", default=[]) or []
codes: List[Optional[int]] = safe_get(forecast, "hourly", "weather_code", default=[]) or []
cur_time: Optional[str] = safe_get(forecast, "current", "time")
idx = 0
if cur_time and times:
# Find nearest index by absolute time difference
try:
ct = datetime.fromisoformat(cur_time)
diffs = []
for t in times:
try:
diffs.append(abs((datetime.fromisoformat(t) - ct).total_seconds()))
except Exception:
diffs.append(float("inf"))
idx = min(range(len(diffs)), key=lambda i: diffs[i]) if diffs else 0
except Exception:
# If parsing fails, try exact match fallback
idx = times.index(cur_time) if cur_time in times else 0
# Prefer nearest code if valid, else first non-null code in window
hcode = None
if isinstance(codes, list) and codes:
if idx < len(codes) and isinstance(codes[idx], (int, float)):
hcode = int(codes[idx])
else:
for c in codes:
if isinstance(c, (int, float)):
hcode = int(c)
break
if isinstance(hcode, int):
code = hcode
log_debug(f"Fallback hourly weather_code used: code={code} idx={idx} cur_time={cur_time}")
except Exception as e:
log_debug(f"Hourly code fallback failed: {e}")
# Final fallback: no valid code at all
if not isinstance(code, int) or code < 0:
unavailable = True
log_debug("Weather code invalid; setting status to 'Condition Unavailable'")
# Compute icon/status (override if unavailable)
if unavailable:
icon = WEATHER_ICONS["default"]
status = "Condition Unavailable"
code_for_class = "unavailable"
else:
icon = wmo_to_icon(code, is_day)
status = wmo_to_status(code)
code_for_class = f"wmo-{code} {'day' if is_day else 'night'}"
# min/max today (index 0)
tmin_val = safe_get(daily, "temperature_2m_min", 0)
tmax_val = safe_get(daily, "temperature_2m_max", 0)
dtemp_unit = daily_units.get("temperature_2m_min", temp_unit)
tmin_str = f"{int(round(tmin_val))}{dtemp_unit}" if isinstance(tmin_val, (int, float)) else ""
tmax_str = f"{int(round(tmax_val))}{dtemp_unit}" if isinstance(tmax_val, (int, float)) else ""
min_max = f"{tmin_str}\t\t{tmax_str}" if tmin_str and tmax_str else ""
wind_val = cur.get("wind_speed_10m")
wind_unit = cur_units.get("wind_speed_10m", "")
wind_text = f"{int(round(wind_val))}{wind_unit}" if isinstance(wind_val, (int, float)) else ""
hum_val = cur.get("relative_humidity_2m")
humidity_text = f"{int(hum_val)}%" if isinstance(hum_val, (int, float)) else ""
vis_val = cur.get("visibility")
visibility_text = f"{format_visibility(vis_val)}" if isinstance(vis_val, (int, float)) else ""
aqi_val = safe_get(aqi or {}, "current", "european_aqi")
aqi_text = f"AQI {int(aqi_val)}" if isinstance(aqi_val, (int, float)) else "AQI N/A"
hourly_precip = build_hourly_precip(forecast)
prediction = f"\n\n{hourly_precip}" if hourly_precip else ""
# Build place string (priority: MANUAL_PLACE > ENV_PLACE > reverse geocode > lat,lon)
place_str = (MANUAL_PLACE or ENV_PLACE or place or f"{lat:.3f}, {lon:.3f}")
location_text = f"{LOC_ICON} {place_str}"
# Build tooltip (markup or plain)
if TOOLTIP_MARKUP:
# Escape dynamic text to avoid breaking Pango markup
tooltip_text = str.format(
"\t\t{}\t\t\n{}\n{}\n{}\n{}\n\n{}\n{}\n{}{}",
f'<span size="xx-large" foreground="{temp_col}">{esc(temp_str)}</span>',
f"<big> {icon}</big>",
f"<b>{esc(status)}</b>",
esc(location_text),
f"<small>{esc(feels_str)}</small>" if feels_str else "",
f"<b>{esc(min_max)}</b>" if min_max else "",
f"{esc(wind_text)}\t{esc(humidity_text)}",
f"{esc(visibility_text)}\t{esc(aqi_text)}",
f"<i> {esc(prediction)}</i>" if prediction else "",
)
else:
lines = [
f"{icon} {temp_str}",
status,
location_text,
]
if feels_str:
lines.append(feels_str)
if min_max:
lines.append(min_max)
lines.append(f"{wind_text} {humidity_text}".strip())
lines.append(f"{visibility_text} {aqi_text}".strip())
if prediction:
lines.append(hourly_precip)
tooltip_text = "\n".join([ln for ln in lines if ln])
# Build main text with colorful icon and colored temp; prefer larger icon if emoji
if ACTIVE_ICON_STYLE == "emoji":
icon_text = f"<span size=\"x-large\">{icon}</span>"
else:
# Colorize mono icons subtly
icon_text = f"<span foreground=\"{temp_col}\">{icon}</span>"
temp_text = f"<span foreground=\"{temp_col}\">{esc(temp_str)}</span>"
out_data = {
"text": f"{icon_text} {temp_text}",
"alt": status,
"tooltip": tooltip_text,
"class": code_for_class,
}
simple_weather = (
f"{icon} {status}\n"
+ f"{temp_str} ({feels_str})\n"
+ (f"{wind_text} \n" if wind_text else "")
+ (f"{humidity_text} \n" if humidity_text else "")
+ f"{visibility_text} {aqi_text}\n"
)
return out_data, simple_weather
def main() -> None:
lat, lon = get_coords()
# Try cache first
cached = read_api_cache()
if cached and isinstance(cached, dict):
forecast = cached.get("forecast")
aqi = cached.get("aqi")
cached_place = cached.get("place") if isinstance(cached.get("place"), str) else None
place_effective = MANUAL_PLACE or ENV_PLACE or cached_place
try:
out, simple = build_output(lat, lon, forecast, aqi, place_effective)
print(json.dumps(out, ensure_ascii=False))
write_simple_text_cache(simple)
return
except Exception as e:
print(f"Cached data build failed, refetching: {e}", file=sys.stderr)
# Fetch fresh
try:
forecast = fetch_open_meteo(lat, lon)
aqi = fetch_aqi(lat, lon)
# Use manual/env place if provided; otherwise reverse geocode
place_effective = MANUAL_PLACE or ENV_PLACE or fetch_place(lat, lon)
write_api_cache({"forecast": forecast, "aqi": aqi, "place": place_effective})
out, simple = build_output(lat, lon, forecast, aqi, place_effective)
print(json.dumps(out, ensure_ascii=False))
write_simple_text_cache(simple)
except Exception as e:
print(f"Open-Meteo fetch failed: {e}", file=sys.stderr)
# Last resort: try stale cache without TTL
try:
if os.path.exists(API_CACHE_PATH):
with open(API_CACHE_PATH, "r", encoding="utf-8") as f:
stale = json.load(f)
out, simple = build_output(lat, lon, stale.get("forecast", {}), stale.get("aqi"), stale.get("place") if isinstance(stale.get("place"), str) else None)
print(json.dumps(out, ensure_ascii=False))
write_simple_text_cache(simple)
return
except Exception as e2:
print(f"Failed to use stale cache: {e2}", file=sys.stderr)
# Fallback minimal output
fallback = {
"text": f"{WEATHER_ICONS['default']} N/A",
"alt": "Unavailable",
"tooltip": "Weather unavailable",
"class": "unavailable",
}
print(json.dumps(fallback, ensure_ascii=False))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# Original script by Eric Murphy
# https://github.com/ericmurphyxyz/dotfiles/blob/master/.local/bin/battery-alert
#
# Modified by Jesse Mirabel (@sejjy)
# https://github.com/sejjy/mechabar
# This script sends a notification when the battery is full, low, or critical.
# icon theme used: tela-circle-icon-theme-dracula
#
# (see the bottom of the script for more information)
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus"
# battery levels
WARNING_LEVEL=20
CRITICAL_LEVEL=10
# get the battery state and percentage using upower (waybar dependency)
BAT_PATH=$(upower -e | grep BAT | head -n 1)
BATTERY_STATE=$(upower -i "$BAT_PATH" | awk '/state:/ {print $2}')
BATTERY_LEVEL=$(upower -i "$BAT_PATH" | awk '/percentage:/ {print $2}' | tr -d '%')
# prevent multiple notifications
FILE_FULL=/tmp/battery-full
FILE_WARNING=/tmp/battery-warning
FILE_CRITICAL=/tmp/battery-critical
# remove the files if the battery is no longer in that state
if [ "$BATTERY_STATE" == "discharging" ]; then
rm -f $FILE_FULL
elif [ "$BATTERY_STATE" == "charging" ]; then
rm -f "$FILE_WARNING" "$FILE_CRITICAL"
fi
# if the battery is full and is plugged in
if [ "$BATTERY_LEVEL" -eq 100 ] && [ "$BATTERY_STATE" == "fully-charged" ] && [ ! -f $FILE_FULL ]; then
notify-send -a "state" "Battery Charged (${BATTERY_LEVEL}%)" "You might want to unplug your PC." -i "battery-full" -r 9991
touch $FILE_FULL
# if the battery is low and is discharging
elif [ "$BATTERY_LEVEL" -le $WARNING_LEVEL ] && [ "$BATTERY_STATE" == "discharging" ] && [ ! -f $FILE_WARNING ]; then
notify-send -a "state" "Battery Low (${BATTERY_LEVEL}%)" "You might want to plug in your PC." -u critical -i "battery-caution" -r 9991 -h string:fgcolor:\#fab387 -h string:frcolor:\#fab387
touch $FILE_WARNING
# if the battery is critical and is discharging
elif [ "$BATTERY_LEVEL" -le $CRITICAL_LEVEL ] && [ "$BATTERY_STATE" == "discharging" ] && [ ! -f $FILE_CRITICAL ]; then
notify-send -a "state" "Battery Critical (${BATTERY_LEVEL}%)" "Plug in your PC now." -u critical -i "battery-empty" -r 9991
touch $FILE_CRITICAL
fi
# systemd service
# Add the following to ~/.config/systemd/user/battery-level.service:
# [Unit]
# Description=Battery Level Checker
# After=graphical.target
#
# [Service]
# ExecStart=%h/.config/waybar/scripts/battery-level.sh
# Type=oneshot
# systemd timer
# Add the following to ~/.config/systemd/user/battery-level.timer:
# [Unit]
# Description=Run Battery Level Checker
#
# [Timer]
# OnBootSec=1min
# OnUnitActiveSec=1min
# Unit=battery-level.service
#
# [Install]
# WantedBy=timers.target
# enable the timer by running the following commands:
# systemctl --user daemon-reload
# systemctl --user enable --now battery-level.timer

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Original script by Eric Murphy
# https://github.com/ericmurphyxyz/dotfiles/blob/master/.local/bin/battery-alert
#
# Modified by Jesse Mirabel (@sejjy)
# https://github.com/sejjy/mechabar
# This script sends a notification when the battery is charging or discharging.
# icon theme used: tela-circle-icon-theme-dracula
#
# (see the bottom of the script for more information)
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus"
# get the battery state from the udev rule
BATTERY_STATE=$1
# get the battery percentage using upower (waybar dependency)
BAT_PATH=$(upower -e | grep BAT | head -n 1)
BATTERY_LEVEL=$(upower -i "$BAT_PATH" | awk '/percentage:/ {print $2}' | tr -d '%')
# set the battery charging state and icon
case "$BATTERY_STATE" in
"charging")
BATTERY_CHARGING="Charging"
BATTERY_ICON="090-charging"
;;
"discharging")
BATTERY_CHARGING="Disharging"
BATTERY_ICON="090"
;;
esac
# send the notification
notify-send -a "state" "Battery ${BATTERY_CHARGING} (${BATTERY_LEVEL}%)" -u normal -i "battery-${BATTERY_ICON}" -r 9991
# udev rule
# Add the following to /etc/udev/rules.d/60-power.rules:
# ACTION=="change", SUBSYSTEM=="power_supply", ATTR{type}=="Mains", ATTR{online}=="0", ENV{DISPLAY}=":0", RUN+="/usr/bin/su <username> -c '$HOME/.config/waybar/scripts/battery-state.sh discharging'"
# ACTION=="change", SUBSYSTEM=="power_supply", ATTR{type}=="Mains", ATTR{online}=="1", ENV{DISPLAY}=":0", RUN+="/usr/bin/su <username> -c '$HOME/.config/waybar/scripts/battery-state.sh charging'"
# the number 60 in the udev rule can be changed to any number between 0 and 99.
# the lower the number, the higher the priority.
#
# $USER does not work, so you have to replace "<username>" with your username.
# reload udev rules by running the following command:
# sudo udevadm control --reload-rules

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
# Print error message for invalid arguments
print_error() {
cat <<"EOF"
Usage: ./brightnesscontrol.sh <action>
Valid actions are:
i -- <i>ncrease brightness [+2%]
d -- <d>ecrease brightness [-2%]
EOF
}
# Send a notification with brightness info
send_notification() {
brightness=$(brightnessctl info | grep -oP "(?<=\()\d+(?=%)")
notify-send -a "state" -r 91190 -i "gpm-brightness-lcd" -h int:value:"$brightness" "Brightness: ${brightness}%" -u low
}
# Get the current brightness percentage and device name
get_brightness() {
brightness=$(brightnessctl -m | grep -o '[0-9]\+%' | head -c-2)
device=$(brightnessctl -m | head -n 1 | awk -F',' '{print $1}' | sed 's/_/ /g; s/\<./\U&/g') # Get device name
current_brightness=$(brightnessctl -m | head -n 1 | awk -F',' '{print $3}') # Get current brightness
max_brightness=$(brightnessctl -m | head -n 1 | awk -F',' '{print $5}') # Get max brightness
}
get_brightness
# Handle options
while getopts o: opt; do
case "${opt}" in
o)
case $OPTARG in
i) # Increase brightness
if [[ $brightness -lt 10 ]]; then
brightnessctl set +1%
else
brightnessctl set +2%
fi
send_notification
;;
d) # Decrease brightness
if [[ $brightness -le 1 ]]; then
brightnessctl set 1%
elif [[ $brightness -le 10 ]]; then
brightnessctl set 1%-
else
brightnessctl set 2%-
fi
send_notification
;;
*)
print_error
;;
esac
;;
*)
print_error
;;
esac
done
# Determine the icon based on brightness level
get_icon() {
if ((brightness <= 5)); then
icon=""
elif ((brightness <= 15)); then
icon=""
elif ((brightness <= 30)); then
icon=""
elif ((brightness <= 45)); then
icon=""
elif ((brightness <= 55)); then
icon=""
elif ((brightness <= 65)); then
icon=""
elif ((brightness <= 80)); then
icon=""
elif ((brightness <= 95)); then
icon=""
else
icon=""
fi
}
# Backlight module and tooltip
get_icon
module="${icon} ${brightness}%"
tooltip="Device Name: ${device}"
tooltip+="\nBrightness: ${current_brightness} / ${max_brightness}"
echo "{\"text\": \"${module}\", \"tooltip\": \"${tooltip}\"}"

View File

@@ -0,0 +1,67 @@
#!/usr/bin/env bash
model=$(awk -F ': ' '/model name/{print $2}' /proc/cpuinfo | head -n 1 | sed 's/@.*//; s/ *\((R)\|(TM)\)//g; s/^[ \t]*//; s/[ \t]*$//')
# get CPU clock speeds
get_cpu_frequency() {
freqlist=$(awk '/cpu MHz/ {print $4}' /proc/cpuinfo)
maxfreq=$(sed 's/...$//' /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq)
if [ -z "$freqlist" ] || [ -z "$maxfreq" ]; then
echo "--"
return
fi
average_freq=$(echo "$freqlist" | tr ' ' '\n' | awk "{sum+=\$1} END {printf \"%.0f/%s MHz\", sum/NR, $maxfreq}")
echo "$average_freq"
}
# get CPU temp
get_cpu_temperature() {
temp=$(sensors | awk '/Package id 0/ {print $4}' | awk -F '[+.]' '{print $2}')
if [[ -z "$temp" ]]; then
temp=$(sensors | awk '/Tctl/ {print $2}' | tr -d '+°C')
fi
if [[ -z "$temp" ]]; then
temp="--"
temp_f="--"
else
temp=${temp%.*}
temp_f=$(awk "BEGIN {printf \"%.1f\", ($temp * 9 / 5) + 32}")
fi
# Celsius and Fahrenheit
echo "${temp:---} ${temp_f:---}"
}
get_temperature_icon() {
temp_value=$1
if [ "$temp_value" = "--" ]; then
icon="󱔱" # none
elif [ "$temp_value" -ge 80 ]; then
icon="󰸁" # high
elif [ "$temp_value" -ge 70 ]; then
icon="󱃂" # medium
elif [ "$temp_value" -ge 60 ]; then
icon="󰔏" # normal
else
icon="󱃃" # low
fi
echo "$icon"
}
cpu_frequency=$(get_cpu_frequency)
read -r temp_info < <(get_cpu_temperature)
temp=$(echo "$temp_info" | awk '{print $1}')
temp_f=$(echo "$temp_info" | awk '{print $2}')
thermo_icon=$(get_temperature_icon "$temp")
# high temp warning
if [ "$temp" == "--" ] || [ "$temp" -ge 80 ]; then
text_output="<span color='#f38ba8'>${thermo_icon} ${temp}°C</span>"
else
text_output="${thermo_icon} ${temp}°C"
fi
tooltip=":: ${model}\n"
tooltip+="Clock Speed: ${cpu_frequency}\nTemperature: ${temp_f}°F"
# module and tooltip
echo "{\"text\": \"$text_output\", \"tooltip\": \"$tooltip\"}"

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Use a dedicated narrow dark theme for the power menu
config="$HOME/.config/rofi/power-menu.rasi"
actions=$(echo -e " Lock\n Shutdown\n Reboot\n Suspend\n Hibernate\n󰞘 Logout")
# Display logout menu
selected_option=$(echo -e "$actions" | rofi -dmenu -i -p "Power Menu" -config "${config}" || pkill -x rofi)
# Perform actions based on the selected option
case "$selected_option" in
*Lock)
loginctl lock-session
;;
*Shutdown)
systemctl poweroff
;;
*Reboot)
systemctl reboot
;;
*Suspend)
systemctl suspend
;;
*Hibernate)
systemctl hibernate
;;
*Logout)
loginctl kill-session "$XDG_SESSION_ID"
;;
esac

View File

@@ -0,0 +1,123 @@
#!/usr/bin/env bash
# Define functions
print_error() {
cat <<"EOF"
Usage: ./volumecontrol.sh -[device] <actions>
...valid devices are...
i -- input device
o -- output device
p -- player application
...valid actions are...
i -- increase volume [+2]
d -- decrease volume [-2]
m -- mute [x]
EOF
exit 1
}
icon() {
vol=$(pactl get-sink-volume @DEFAULT_SINK@ | awk '{print $5}' | sed 's/%//')
mute=$(pactl get-sink-mute @DEFAULT_SINK@ | awk '{print $2}')
if [ "$mute" = "yes" ] || [ "$vol" -eq 0 ]; then
icon="volume-level-muted"
elif [ "$vol" -lt 33 ]; then
icon="volume-level-low"
elif [ "$vol" -lt 66 ]; then
icon="volume-level-medium"
else
icon="volume-level-high"
fi
}
send_notification() {
icon
notify-send -a "state" -r 91190 -i "$icon" -h int:value:"$vol" "Volume: ${vol}%" -u low
}
notify_mute() {
mute=$(pactl get-sink-mute @DEFAULT_SINK@ | awk '{print $2}')
if [ "$mute" = "yes" ]; then
notify-send -a "state" -r 91190 -i "volume-level-muted" "Volume: Muted" -u low
else
icon
notify-send -a "state" -r 91190 -i "$icon" "Volume: Unmuted" -u low
fi
}
action_volume() {
case "${1}" in
i)
# Increase volume if below 100
current_vol=$(pactl get-sink-volume @DEFAULT_SINK@ | awk '{print $5}' | sed 's/%//')
if [ "$current_vol" -lt 100 ]; then
new_vol=$((current_vol + 2))
[ "$new_vol" -gt 100 ] && new_vol=100
pactl set-sink-volume @DEFAULT_SINK@ "${new_vol}%"
fi
;;
d)
# Decrease volume if above 0
current_vol=$(pactl get-sink-volume @DEFAULT_SINK@ | awk '{print $5}' | sed 's/%//')
new_vol=$((current_vol - 2))
[ "$new_vol" -lt 0 ] && new_vol=0
pactl set-sink-volume @DEFAULT_SINK@ "${new_vol}%"
;;
esac
}
select_output() {
if [ "$@" ]; then
desc="$*"
device=$(pactl list sinks | grep -C2 -F "Description: $desc" | grep Name | cut -d: -f2 | xargs)
if pactl set-default-sink "$device"; then
notify-send -r 91190 "Activated: $desc"
else
notify-send -r 91190 "Error activating $desc"
fi
else
pactl list sinks | grep -ie "Description:" | awk -F ': ' '{print $2}' | sort
fi
}
# Evaluate device option
while getopts iops: DeviceOpt; do
case "${DeviceOpt}" in
i)
nsink=$(pactl list sources short | awk '{print $2}')
[ -z "${nsink}" ] && echo "ERROR: Input device not found..." && exit 0
srce="--default-source"
;;
o)
nsink=$(pactl list sinks short | awk '{print $2}')
[ -z "${nsink}" ] && echo "ERROR: Output device not found..." && exit 0
srce=""
;;
p)
nsink=$(playerctl --list-all | grep -w "${OPTARG}")
[ -z "${nsink}" ] && echo "ERROR: Player ${OPTARG} not active..." && exit 0
# shellcheck disable=SC2034
srce="${nsink}"
;;
s)
# Select an output device
select_output "$@"
exit
;;
*) print_error ;;
esac
done
# Set default variables
shift $((OPTIND - 1))
# Execute action
case "${1}" in
i) action_volume i ;;
d) action_volume d ;;
m) pactl set-sink-mute @DEFAULT_SINK@ toggle && notify_mute && exit 0 ;;
*) print_error ;;
esac
send_notification

View File

@@ -0,0 +1,177 @@
#!/usr/bin/env bash
# This script gathers detailed Wi-Fi connection information.
# It collects the following fields:
#
# - SSID (Service Set Identifier): The name of the Wi-Fi network you
# are currently connected to. Example: "My_Network"
#
# - IP Address: The IP address assigned to the device by the router.
# This is typically a private IP within the local network. Example:
# "192.168.1.29/24" (with subnet mask)
#
# - Router (Gateway): The IP address of the router (default gateway)
# that your device uses to communicate outside the local network.
# Example: "192.168.1.1"
#
# - MAC Address: The unique Media Access Control address of the local
# device's Wi-Fi adapter. Example: "F8:34:41:07:1B:65"
#
# - Security: The encryption protocol being used to secure your Wi-Fi
# connection. Common security protocols include:
# - WPA2 (Wi-Fi Protected Access 2): The most commonly used security
# standard, offering strong encryption (AES).
# - WPA3: The latest version, providing even stronger security,
# especially in public or open networks.
# - WEP (Wired Equivalent Privacy): An outdated and insecure protocol
# that should not be used.
# Example: "WPA2" indicates that the connection is secured using WPA2
# with AES encryption.
#
# - BSSID (Basic Service Set Identifier): The MAC address of the Wi-Fi
# access point you are connected to. Example: "A4:22:49:DA:91:A0"
#
# - Channel: The wireless channel your Wi-Fi network is using. This is
# associated with the frequency band. Example: "100 (5500 MHz)"
# indicates the channel number (100) and the frequency (5500 MHz),
# which is within the 5 GHz band.
#
# - RSSI (Received Signal Strength Indicator): The strength of the
# Wi-Fi signal, typically in dBm (decibels relative to 1 milliwatt).
# Closer to 0 means stronger signal, with values like -40 dBm being
# very good. Example: "-40 dBm"
#
# - Signal: The signal quality, which is represented as a percentage,
# where higher numbers mean better signal. Example: "100"
# indicates perfect signal strength.
#
# - Rx Rate (Receive Rate): The maximum data rate (in Mbit/s) at which
# the device can receive data from the Wi-Fi access point. Example:
# "866.7 MBit/s" indicates a high-speed connection on a modern
# standard.
#
# - Tx Rate (Transmit Rate): The maximum data rate (in Mbit/s) at
# which the device can send data to the Wi-Fi access point. Example:
# "866.7 MBit/s"
#
# - PHY Mode (Physical Layer Mode): The Wi-Fi protocol or standard in
# use. Common modes include 802.11n, 802.11ac, and 802.11ax (Wi-Fi
# 6). Example: "802.11ac" indicates you're using the 5 GHz band with
# a modern high-speed standard.
if ! command -v nmcli &>/dev/null; then
echo "{\"text\": \"<span color='#f38ba8'>󰤫</span>\", \"tooltip\": \"nmcli utility is missing\"}"
exit 1
fi
# Check if Wi-Fi is enabled
wifi_status=$(nmcli radio wifi)
if [ "$wifi_status" = "disabled" ]; then
echo "{\"text\": \"󰤮\", \"tooltip\": \"Wi-Fi Disabled\"}"
exit 0
fi
wifi_info=$(nmcli -t -f active,ssid,signal,security dev wifi | grep "^yes")
# If no ESSID is found, set a default value
if [ -z "$wifi_info" ]; then
essid="No Connection"
signal=0
tooltip="No Connection"
else
# Some defaults
ip_address="127.0.0.1"
# gateway="127.0.0.1"
# mac_address="N/A"
security=$(echo "$wifi_info" | awk -F: '{print $4}')
# bssid="N/A"
chan="N/A"
# rssi="N/A"
# rx_bitrate=""
# tx_bitrate=""
# phy_mode=""
signal=$(echo "$wifi_info" | awk -F: '{print $3}')
active_device=$(nmcli -t -f DEVICE,STATE device status |
grep -w "connected" |
grep -v -E "^(dummy|lo:|virbr0)" |
awk -F: '{print $1}' |
head -n 1)
if [ -n "$active_device" ]; then
output=$(nmcli -e no -g ip4.address,ip4.gateway,general.hwaddr device show "$active_device")
ip_address=$(echo "$output" | sed -n '1p')
# gateway=$(echo "$output" | sed -n '2p')
# mac_address=$(echo "$output" | sed -n '3p')
line=$(nmcli -e no -t -f active,bssid,chan,freq device wifi | grep "^yes")
# bssid=$(echo "$line" | awk -F':' '{print $2":"$3":"$4":"$5":"$6":"$7}')
chan=$(echo "$line" | awk -F':' '{print $8}')
freq=$(echo "$line" | awk -F':' '{print $9}')
chan="$chan ($freq)"
# if command -v iw &>/dev/null; then
# iw_output=$(iw dev "$active_device" station dump)
# rssi=$(echo "$iw_output" | grep "signal:" | awk '{print $2 " dBm"}')
# Upload speed
# rx_bitrate=$(echo "$iw_output" | grep "rx bitrate:" | awk '{print $3 " " $4}')
# Download speed
# tx_bitrate=$(echo "$iw_output" | grep "tx bitrate:" | awk '{print $3 " " $4}')
# Physical Layer Mode
# if echo "$iw_output" | grep -E -q "rx bitrate:.* VHT"; then
# phy_mode="802.11ac" # Wi-Fi 5
# elif echo "$iw_output" | grep -E -q "rx bitrate:.* HT"; then
# phy_mode="802.11n" # Wi-Fi 4
# elif echo "$iw_output" | grep -E -q "rx bitrate:.* HE"; then
# phy_mode="802.11ax" # Wi-Fi 6
# fi
# fi
# Get the current Wi-Fi ESSID
essid=$(echo "$wifi_info" | awk -F: '{print $2}')
tooltip=":: ${essid}"
tooltip+="\nIP Address: ${ip_address}"
# tooltip+="\nRouter: ${gateway}"
# tooltip+="\nMAC Address: ${mac_address}"
tooltip+="\nSecurity: ${security}"
# tooltip+="\nBSSID: ${bssid}"
tooltip+="\nChannel: ${chan}"
# tooltip+="\nRSSI: ${rssi}"
tooltip+="\nStrength: ${signal} / 100"
# if [ -n "$rx_bitrate" ]; then
# tooltip+="\nRx Rate: ${rx_bitrate}"
# fi
# if [ -n "$tx_bitrate" ]; then
# tooltip+="\nTx Rate: ${tx_bitrate}"
# fi
# if [ -n "$phy_mode" ]; then
# tooltip+="\nPHY Mode: ${phy_mode}"
# fi
fi
fi
# Determine Wi-Fi icon based on signal strength
if [ "$signal" -ge 80 ]; then
icon="󰤨" # Strong signal
elif [ "$signal" -ge 60 ]; then
icon="󰤥" # Good signal
elif [ "$signal" -ge 40 ]; then
icon="󰤢" # Weak signal
elif [ "$signal" -ge 20 ]; then
icon="󰤟" # Very weak signal
else
icon="<span color='#f38ba8'>󰤯</span>" # No signal
fi
# Module and tooltip
echo "{\"text\": \"${icon}\", \"tooltip\": \"${tooltip}\"}"

View File

@@ -0,0 +1,563 @@
{
pkgs,
lib,
host,
config,
...
}: let
inherit (import ../../../hosts/${host}/variables.nix) clock24h;
scriptsDir = ./scripts;
scripts = builtins.attrNames (builtins.readDir scriptsDir);
in
with lib; {
# Install any helper scripts shipped in modules/home/waybar/scripts into ~/.config/waybar/scripts
home.file = builtins.listToAttrs (map
(name: {
name = ".config/waybar/scripts/" + name;
value = {
source = "${scriptsDir}/${name}";
executable = true;
};
})
scripts);
programs.waybar = {
enable = true;
package = pkgs.waybar;
settings = [
{
layer = "top";
position = "top";
exclusive = true;
passthrough = false;
"fixed-center" = true;
ipc = true;
height = 34;
spacing = 0;
margin = 2;
"margin-top" = 0;
"margin-bottom" = 0;
"margin-left" = 0;
"margin-right" = 0;
modules-left = [
"custom/startmenu"
"custom/qs_wallpapers_apply"
"custom/qs_vid_wallpapers_apply"
"hyprland/workspaces"
"idle_inhibitor"
"tray"
"mpd#2"
"mpd#3"
"mpd#4"
"mpd"
];
modules-center = ["cpu" "cpu#2" "memory" "memory#2" "disk" "disk#2"];
modules-right = [
"custom/themes"
"pulseaudio"
"pulseaudio#2"
"backlight"
"backlight#2"
"battery"
"battery#2"
"bluetooth"
"bluetooth#2"
"network"
"network#2"
"clock"
"clock#2"
"custom/power"
];
# --- Modules (ported from provided BlackDog config) ---
backlight = {
interval = 2;
format = "{icon}";
"format-icons" = ["" "" "" "" "" "" ""];
"on-scroll-up" = "light -A 5%";
"on-scroll-down" = "light -U 5%";
"smooth-scrolling-threshold" = 1;
};
"backlight#2" = {
interval = 2;
format = "{percent}%";
"on-scroll-up" = "light -A 5%";
"on-scroll-down" = "light -U 5%";
"smooth-scrolling-threshold" = 1;
};
battery = {
interval = 60;
"full-at" = 100;
"design-capacity" = false;
states = {
good = 95;
warning = 30;
critical = 15;
};
format = "{icon}";
"format-charging" = "";
"format-plugged" = "";
"format-full" = "";
"format-icons" = ["" "" "" "" "" "" "" "" ""];
"format-time" = "{H}h {M}min";
tooltip = true;
};
"battery#2" = {
interval = 60;
"full-at" = 100;
"design-capacity" = false;
states = {
good = 95;
warning = 30;
critical = 15;
};
format = "{capacity}%";
"format-charging" = "{capacity}%";
"format-plugged" = "{capacity}%";
"format-full" = "Full";
"format-alt" = "{time}";
"format-time" = "{H}h {M}min";
tooltip = true;
};
bluetooth = {
format = "";
"format-on" = "";
"format-off" = "";
"format-disabled" = "";
"format-connected" = "";
"format-connected-battery" = "";
tooltip = true;
"tooltip-format" = "{controller_alias}\t{controller_address}";
"tooltip-format-connected" = "{controller_alias}\t{controller_address}\n\n{device_enumerate}";
"tooltip-format-enumerate-connected" = "{device_alias}\t{device_address}";
"on-click" = "$HOME/.config/hypr/scripts/rofi_bluetooth";
"on-click-right" = "blueman-manager";
};
"bluetooth#2" = {
format = "{status}";
"format-on" = "{status}";
"format-off" = "{status}";
"format-disabled" = "{status}";
"format-connected" = "{device_alias}";
"format-connected-battery" = "{device_alias}, {device_battery_percentage}%";
tooltip = true;
"tooltip-format" = "{controller_alias}\t{controller_address}";
"tooltip-format-connected" = "{controller_alias}\t{controller_address}\n\n{device_enumerate}";
"tooltip-format-enumerate-connected" = "{device_alias}\t{device_address}";
"on-click" = "$HOME/.config/hypr/scripts/rofi_bluetooth";
"on-click-right" = "blueman-manager";
};
clock = {
"tooltip-format" = "<big>{:%B %Y}</big>\n<tt><small>{calendar}</small></tt>";
format = "";
};
"clock#2" = {
interval = 60;
"tooltip-format" = "<big>{:%B %Y}</big>\n<tt><small>{calendar}</small></tt>";
format =
if clock24h
then "{:%H:%M}"
else "{:%I:%M %p}";
"format-alt" = "{:%a %b %d, %G}";
};
cpu = {
interval = 5;
format = "";
};
"cpu#2" = {
interval = 5;
format = "{usage}%";
};
"custom/themes" = {
format = "";
tooltip = false;
"on-click" = "$HOME/.config/hypr/theme/theme.sh --pywal";
"on-click-right" = "$HOME/.config/hypr/theme/theme.sh --default";
};
# Start menu aligned to project standard
"custom/startmenu" = {
tooltip = true;
"tooltip-format" = "App menu";
format = "";
on-click = "launch-nwg-menu";
"on-click-right" = "nwg-drawer -mr 225 -ml 225 -mt 200 -mb 200 -is 48 --spacing 15";
};
# Power menu aligned to project standard (qs-wlogout + rofi fallback)
"custom/power" = {
tooltip = true;
"tooltip-format" = "Left Click: Power Menu (qs-wlogout)\nRight Click: Rofi Power Menu";
format = " ";
on-click = "qs-wlogout";
"on-click-right" = "~/.config/waybar/scripts/power-menu.sh";
};
disk = {
interval = 30;
format = "";
};
"disk#2" = {
interval = 30;
format = "{free}";
};
memory = {
interval = 10;
format = "";
};
"memory#2" = {
interval = 10;
format = "{used:0.1f}G";
};
# Optional Spotify module (kept from source; may require script)
"custom/spotify" = {
exec = "$HOME/.config/hypr/waybar/spotify";
interval = 1;
format = "{}";
tooltip = true;
"max-length" = 40;
"on-click" = "playerctl play-pause";
"on-click-middle" = "playerctl previous";
"on-click-right" = "playerctl next";
"on-scroll-up" = "playerctl position 05+";
"on-scroll-down" = "playerctl position 05-";
"smooth-scrolling-threshold" = 1;
};
# MPD transport cluster
mpd = {
interval = 2;
"unknown-tag" = "N/A";
format = "{artist} - {title} | {elapsedTime:%M:%S}";
"format-disconnected" = "Disconnected";
"format-paused" = "{artist} - {title}";
"format-stopped" = "Stopped";
"tooltip-format" = "MPD (connected)";
"tooltip-format-disconnected" = "MPD (disconnected)";
"on-click" = "mpc toggle";
"on-scroll-up" = "mpc seek +00:00:01";
"on-scroll-down" = "mpc seek -00:00:01";
"smooth-scrolling-threshold" = 1;
};
"mpd#2" = {
format = "";
"format-disconnected" = "";
"format-paused" = "";
"format-stopped" = "";
"on-click" = "mpc prev";
};
"mpd#3" = {
interval = 1;
format = "{stateIcon}";
"format-disconnected" = "";
"format-paused" = "{stateIcon}";
"format-stopped" = "";
"state-icons" = {
paused = "";
playing = "";
};
"on-click" = "mpc toggle";
};
"mpd#4" = {
format = "";
"format-disconnected" = "";
"format-paused" = "";
"format-stopped" = "";
"on-click" = "mpc next";
};
network = {
interval = 5;
"format-wifi" = "";
"format-ethernet" = "";
"format-linked" = "";
"format-disconnected" = "";
"format-disabled" = "";
"tooltip-format" = " {ifname} via {gwaddr}";
"on-click" = "$HOME/.config/hypr/scripts/rofi_network";
};
"network#2" = {
interval = 5;
"format-wifi" = "{essid}";
"format-ethernet" = "{ipaddr}/{cidr}";
"format-linked" = "{ifname} (No IP)";
"format-disconnected" = "Disconnected";
"format-disabled" = "Disabled";
"format-alt" = " {bandwidthUpBits} | {bandwidthDownBits}";
"tooltip-format" = " {ifname} via {gwaddr}";
};
pulseaudio = {
format = "{icon}";
"format-muted" = "";
"format-bluetooth" = "";
"format-bluetooth-muted" = "";
"format-source" = "";
"format-source-muted" = "";
"format-icons" = {
headphone = "";
"hands-free" = "";
headset = "";
phone = "";
portable = "";
car = "";
default = ["" "" ""];
};
"scroll-step" = 5.0;
"on-click" = "pulsemixer --toggle-mute";
"on-click-right" = "pulsemixer --toggle-mute";
"smooth-scrolling-threshold" = 1;
};
"pulseaudio#2" = {
format = "{volume}%";
"format-muted" = "Mute";
"format-bluetooth" = "{volume}%";
"format-bluetooth-muted" = "Mute";
"format-source" = "{volume}%";
"scroll-step" = 5.0;
"on-click" = "pulsemixer --toggle-mute";
"on-click-right" = "pulsemixer --toggle-mute";
"smooth-scrolling-threshold" = 1;
};
idle_inhibitor = {
format = "{icon}";
"format-icons" = {
activated = "";
deactivated = "";
};
timeout = 30;
};
"hyprland/workspaces" = {
format = "{icon}";
"sort-by-number" = true;
"active-only" = false;
"format-icons" = {
"1" = "1";
"2" = "2";
"3" = "3";
"4" = "4";
"5" = "5";
"6" = "6";
"7" = "7";
"8" = "8";
"9" = "9";
"10" = "10";
urgent = "";
focused = "";
default = "";
};
on-click = "activate";
};
tray = {
"icon-size" = 16;
spacing = 10;
};
# ddubs wallpaper quick actions
"custom/qs_wallpapers_apply" = {
format = "";
tooltip = true;
"tooltip-format" = "Set image wallpaper";
on-click = "qs-wallpapers-apply";
};
"custom/qs_vid_wallpapers_apply" = {
format = "";
tooltip = true;
"tooltip-format" = "Set video wallpaper";
on-click = "qs-vid-wallpapers-apply";
};
}
];
# --- CSS (colors + styles from provided BlackDog theme, with startmenu selector updated) ---
style = concatStrings [
''
/** Colors (from colors.css) **/
@define-color background #0a0a0a;
@define-color background-alt1 #181818;
@define-color background-alt2 #232323;
@define-color foreground #ddd8d0;
@define-color selected #E29E5D;
@define-color black #0a0a0a;
@define-color red #C53A31;
@define-color green #867971;
@define-color yellow #8F847B;
@define-color blue #E29E5D;
@define-color magenta #7C7987;
@define-color cyan #A19993;
@define-color white #ddd8d0;
/** Fonts **/
* {
font-family: "JetBrains Mono", "Symbols Nerd Font", Iosevka, archcraft, sans-serif;
font-size: 14px;
}
/** Waybar Window **/
window#waybar {
background-color: @background;
color: @foreground;
border-bottom: 2px solid @background-alt1;
transition-property: background-color;
transition-duration: .5s;
}
window#waybar.hidden { opacity: 0.5; }
/** Custom **/
#custom-startmenu {
background-color: @background-alt1;
color: @magenta;
font-size: 18px;
border-radius: 0px 14px 0px 0px;
margin: 0px 0px 0px 0px;
padding: 2px 8px 2px 8px;
}
#custom-themes { background-color: @selected; }
#custom-power {
background-color: @red;
font-size: 16px;
}
#custom-power, #custom-themes {
color: @background;
border-radius: 10px;
margin: 6px 6px 6px 0px;
padding: 2px 8px 2px 8px;
}
/** Idle Inhibitor **/
#idle_inhibitor {
background-color: @green;
color: @background;
border-radius: 10px;
margin: 6px 0px 6px 6px;
padding: 4px 6px;
}
#idle_inhibitor.deactivated { background-color: @red; }
/** Tray **/
#tray {
background-color: @background-alt1;
border-radius: 10px;
margin: 6px 0px 6px 6px;
padding: 4px 6px;
}
#tray > .passive { -gtk-icon-effect: dim; }
#tray > .needs-attention { -gtk-icon-effect: highlight; }
/** MPD **/
@keyframes gradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
#mpd { color: @foreground; font-size: 12px; font-weight: bold; }
#mpd.disconnected, #mpd.stopped { color: @red; }
#mpd.playing { color: @cyan; }
#mpd.2 { border-radius: 10px 0px 0px 10px; margin: 6px 0px 6px 6px; padding: 4px 6px 4px 10px; }
#mpd.3 { margin: 6px 0px 6px 0px; padding: 4px; }
#mpd.4 { border-radius: 0px 10px 10px 0px; margin: 6px 6px 6px 0px; padding: 4px 10px 4px 6px; }
#mpd.2,#mpd.3,#mpd.4 { background-color: @background-alt1; font-size: 14px; }
/** Spotify **/
#custom-spotify {
background-color: @background-alt1;
color: @foreground;
border-radius: 10px;
margin: 6px 0px 6px 6px;
padding: 4px 8px;
font-size: 12px;
font-weight: bold;
}
#custom-spotify.paused { color: @foreground; }
#custom-spotify.playing {
background: linear-gradient(90deg, @magenta 25%, @red 50%, @yellow 75%, @cyan 100%);
background-size: 300% 300%;
animation: gradient 10s ease infinite;
color: @background;
}
#custom-spotify.offline { color: @red; }
/** CPU/Memory/Disk **/
#cpu { color: @red; }
#memory { color: @green; }
#disk { color: @yellow; }
/** Pulseaudio **/
#pulseaudio { color: @blue; }
#pulseaudio.bluetooth { color: @cyan; }
#pulseaudio.muted { color: @red; }
/** Backlight **/
#backlight { color: @magenta; }
/** Battery **/
#battery { color: @cyan; }
@keyframes blink { to { color: @foreground; } }
#battery.critical:not(.charging) { background-color: @background-alt2; }
#battery.2.critical:not(.charging) {
background-color: @background-alt1;
color: @red;
animation-name: blink;
animation-duration: 0.5s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-direction: alternate;
}
/** Network **/
#network { color: @yellow; }
#network.disconnected, #network.disabled { color: @red; }
/** Bluetooth **/
#bluetooth { color: @green; }
#bluetooth.disabled, #bluetooth.off { color: @red; }
/** Clock **/
#clock { color: @blue; }
/** Workspaces **/
#workspaces {
background-color: @background;
border-radius: 10px;
margin-left: 6px;
margin-bottom: 2px;
padding: 0px;
}
#workspaces button { color: @foreground; }
#workspaces button.active { color: @red; }
#workspaces button.urgent { color: @green; }
#workspaces button.hidden { color: @yellow; }
/** Common style **/
#backlight, #battery, #clock, #cpu, #disk, #memory, #pulseaudio, #network, #bluetooth {
background-color: @background-alt2;
border-radius: 10px 0px 0px 10px;
margin: 6px 0px 6px 0px;
padding: 4px 6px;
}
#backlight.2, #battery.2, #clock.2, #cpu.2, #disk.2, #memory.2, #pulseaudio.2, #network.2, #bluetooth.2 {
background-color: @background-alt1;
color: @foreground;
font-size: 12px;
font-weight: bold;
border-radius: 0px 10px 10px 0px;
margin: 6px 6px 6px 0px;
padding: 5px 6px 4px 6px;
}
''
];
};
}

View File

@@ -0,0 +1,356 @@
{
pkgs,
config,
lib,
...
}: let
terminal = "kitty";
base00 = "0F1419";
base01 = "131721";
base03 = "3E4B59";
base05 = "E6E1CF";
base06 = "E6E1CF";
base07 = "F3F4F5";
base08 = "F07178";
base09 = "FF8F40";
base0A = "FFB454";
base0B = "B8CC52";
base0C = "95E6CB";
base0D = "59C2FF";
base0E = "D2A6FF";
base0F = "E6B673";
in
with lib; {
# Configure & Theme Waybar
programs.waybar = {
enable = true;
package = pkgs.waybar;
settings = [
{
layer = "top";
position = "top";
modules-left = ["custom/startmenu" "tray" "hyprland/window"];
modules-center = ["hyprland/workspaces"];
modules-right = ["idle_inhibitor" "custom/notification" "pulseaudio" "battery" "clock" "custom/exit"];
"hyprland/workspaces" = {
format = "{name}";
format-icons = {
default = " ";
active = " ";
urgent = " ";
};
on-scroll-up = "hyprctl dispatch workspace e+1";
on-scroll-down = "hyprctl dispatch workspace e-1";
};
"clock" = {
format = '' {:%H:%M}'';
/*
''{: %I:%M %p}'';
*/
tooltip = true;
tooltip-format = "<big>{:%A, %d.%B %Y }</big><tt><small>{calendar}</small></tt>";
};
"hyprland/window" = {
max-length = 60;
separate-outputs = false;
};
"memory" = {
interval = 5;
format = " {}%";
tooltip = true;
on-click = "${terminal} -e btop";
};
"cpu" = {
interval = 5;
format = " {usage:2}%";
tooltip = true;
on-click = "${terminal} -e btop";
};
"disk" = {
format = " {free}";
tooltip = true;
# Not working with zaneyos window open then closes
#on-click = "${terminal} -e sh -c df -h ; read";
};
"network" = {
format-icons = ["󰤯" "󰤟" "󰤢" "󰤥" "󰤨"];
format-ethernet = " {bandwidthDownBits}";
format-wifi = " {bandwidthDownBits}";
format-disconnected = "󰤮";
tooltip = false;
on-click = "${terminal} -e btop";
};
"tray" = {
spacing = 12;
};
"pulseaudio" = {
format = "{icon} {volume}% {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-icons = {
headphone = "";
hands-free = "";
headset = "";
phone = "";
portable = "";
car = "";
default = ["" "" ""];
};
on-click = "pavucontrol";
};
"custom/exit" = {
tooltip = false;
format = "";
on-click = "sleep 0.1 && wlogout";
};
"custom/startmenu" = {
tooltip = false;
format = " ";
# exec = "rofi -show drun";
on-click = "rofi -show drun";
};
"idle_inhibitor" = {
format = "{icon}";
format-icons = {
activated = " ";
deactivated = " ";
};
tooltip = "true";
};
"custom/notification" = {
tooltip = false;
format = "{icon} {}";
format-icons = {
notification = "<span foreground='red'><sup></sup></span>";
none = "";
dnd-notification = "<span foreground='red'><sup></sup></span>";
dnd-none = "";
inhibited-notification = "<span foreground='red'><sup></sup></span>";
inhibited-none = "";
dnd-inhibited-notification = "<span foreground='red'><sup></sup></span>";
dnd-inhibited-none = "";
};
return-type = "json";
exec-if = "which swaync-client";
exec = "swaync-client -swb";
on-click = "swaync-client -t";
escape = true;
};
"battery" = {
states = {
warning = 30;
critical = 15;
};
format = "{icon} {capacity}%";
format-charging = "󰂄 {capacity}%";
format-plugged = "󱘖 {capacity}%";
format-icons = ["󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹"];
on-click = "";
tooltip = false;
};
}
];
style = concatStrings [
''
* {
font-size: 16pt;
font-family: JetBrainsMono Nerd Font, Font Awesome, sans-serif;
font-weight: bold;
}
window#waybar {
/*
background-color: rgba(26,27,38,0);
border-bottom: 1px solid rgba(26,27,38,0);
border-radius: 0px;
color: #${base0F};
*/
background-color: rgba(26,27,38,0);
border-bottom: 1px solid rgba(26,27,38,0);
border-radius: 0px;
color: #${base0F};
}
#workspaces {
/*
Eternal
background: linear-gradient(180deg, #${base00}, #${base01});
margin: 5px 5px 5px 0px;
padding: 0px 10px;
border-radius: 0px 15px 15px 0px;
border: 0px;
font-style: normal;
color: #${base00};
*/
background: linear-gradient(45deg, #${base01}, #${base01});
margin: 5px;
padding: 0px 1px;
border-radius: 15px;
border: 0px;
font-style: normal;
color: #${base00};
}
#workspaces button {
padding: 0px 5px;
margin: 4px 3px;
border-radius: 15px;
border: 0px;
color: #${base00};
background: linear-gradient(45deg, #${base0D}, #${base0E});
opacity: 0.5;
transition: all 0.3s ease-in-out;
}
#workspaces button.active {
padding: 0px 5px;
margin: 4px 3px;
border-radius: 15px;
border: 0px;
color: #${base00};
background: linear-gradient(45deg, #${base0D}, #${base0E});
opacity: 1.0;
min-width: 40px;
transition: all 0.3s ease-in-out;
}
#workspaces button:hover {
border-radius: 15px;
color: #${base00};
background: linear-gradient(45deg, #${base0D}, #${base0E});
opacity: 0.8;
}
tooltip {
background: #${base00};
border: 1px solid #${base0E};
border-radius: 10px;
}
tooltip label {
color: #${base07};
}
#window {
/*
Eternal
color: #${base05};
background: #${base00};
border-radius: 15px;
margin: 5px;
padding: 2px 20px;
*/
margin: 5px;
padding: 2px 20px;
color: #${base05};
background: #${base01};
border-radius: 15px 15px 15px 15px;
}
#memory {
color: #${base0F};
/*
Eternal
background: #${base00};
border-radius: 15px 15px 15px 15px;
margin: 5px;
padding: 2px 20px;
*/
background: #${base01};
margin: 5px;
padding: 2px 20px;
border-radius: 15px 15px 15px 15px;
}
#clock {
color: #${base0B};
background: #${base00};
border-radius: 15px 15px 15px 15px;
margin: 5px;
padding: 2px 20px;
}
#idle_inhibitor {
color: #${base0A};
background: #${base00};
border-radius: 15px 15px 15px 15px;
margin: 3px;
padding: 2px 20px;
}
#cpu {
color: #${base07};
background: #${base00};
border-radius: 15px 15px 15px 15px;
margin: 5px;
padding: 2px 20px;
}
#disk {
color: #${base0F};
background: #${base00};
border-radius: 15px 15px 15px 15px;
margin: 5px;
padding: 2px 20px;
}
#battery {
color: #${base08};
background: #${base00};
border-radius: 15px 15px 15px 15px;
margin: 5px;
padding: 2px 20px;
}
#network {
color: #${base09};
background: #${base00};
border-radius: 15px 15px 15px 15px;
margin: 5px;
padding: 2px 20px;
}
#tray {
color: #${base05};
background: #${base00};
border-radius: 15px 15px 15px 15px;
margin: 5px;
padding: 2px 15px;
}
#pulseaudio {
color: #${base0D};
/*
Eternal
background: #${base00};
border-radius: 15px 15px 15px 15px;
margin: 5px;
padding: 2px 20px;
*/
background: #${base01};
margin: 4px;
padding: 2px 20px;
border-radius: 15px 15px 15px 15px;
}
#custom-notification {
color: #${base0C};
background: #${base00};
border-radius: 15px 15px 15px 15px;
margin: 5px;
padding: 2px 20px;
}
#custom-startmenu {
color: #${base0E};
background: #${base00};
border-radius: 0px 15px 15px 0px;
margin: 5px 5px 5px 0px;
padding: 2px 20px;
}
#idle_inhibitor {
color: #${base09};
background: #${base00};
border-radius: 15px 15px 15px 15px;
margin: 5px;
padding: 2px 20px;
}
#custom-exit {
color: #${base0E};
background: #${base00};
border-radius: 15px 0px 0px 15px;
margin: 5px 0px 5px 5px;
padding: 2px 20px;
}
''
];
};
}

View File

@@ -0,0 +1,959 @@
{pkgs, ...}: let
# Install any helper scripts shipped in modules/home/waybar/scripts into ~/.config/waybar/scripts
scriptsDir = ./scripts;
scripts = builtins.attrNames (builtins.readDir scriptsDir);
# Inline, improved Cava script packaged via Nix so we don't rely on an external bash file
waybarCava = pkgs.writeShellScriptBin "WaybarCava" ''
set -euo pipefail
# Ensure cava exists
if ! command -v cava >/dev/null 2>&1; then
echo "cava not found in PATH" >&2
exit 1
fi
# Characters for vertical bars (0..7)
bar=""
# Build sed script that:
# - strips semicolons (cava RAW ASCII delimiter)
# - maps digits 0..7 to the corresponding glyph in $bar
dict="s/;//g"
bar_length=''${#bar}
for ((i = 0; i < bar_length; i++)); do
dict+=";s/$i/''${bar:$i:1}/g"
done
# Single-instance guard (kill prior instance cleanly)
RUNTIME_DIR="''${XDG_RUNTIME_DIR:-/tmp}"
pidfile="$RUNTIME_DIR/waybar-cava.pid"
if [ -f "$pidfile" ]; then
oldpid=$(cat "$pidfile" || true)
if [ -n "''${oldpid:-}" ] && kill -0 "$oldpid" 2>/dev/null; then
kill "$oldpid" 2>/dev/null || true
# Give the old pipeline a moment to exit
sleep 0.1 || true
fi
fi
echo $$ > "$pidfile"
# Use a unique temporary config and clean it up on exit
config_file=$(mktemp "''${RUNTIME_DIR}/waybar-cava.XXXXXX.conf")
cleanup() {
rm -f "$config_file" "$pidfile"
}
trap cleanup EXIT INT TERM
cat >"$config_file" <<EOF
[general]
framerate = 30
bars = 10
[input]
method = pulse
source = auto
[output]
method = raw
raw_target = /dev/stdout
data_format = ascii
ascii_max_range = 7
EOF
# Stream cava output and transform
exec cava -p "$config_file" | sed -u "$dict"
'';
# Catppuccin Mocha palette so CSS can be self-contained (no @import needed)
catppuccinColors = {
rosewater = "#f5e0dc";
flamingo = "#f2cdcd";
pink = "#f5c2e7";
mauve = "#cba6f7";
red = "#f38ba8";
maroon = "#eba0ac";
peach = "#fab387";
yellow = "#f9e2af";
green = "#a6e3a1";
teal = "#94e2d5";
sky = "#89dceb";
sapphire = "#74c7ec";
blue = "#89b4fa";
lavender = "#b4befe";
text = "#cdd6f4";
subtext1 = "#bac2de";
subtext0 = "#a6adc8";
overlay2 = "#9399b2";
overlay1 = "#7f849c";
overlay0 = "#6c7086";
surface2 = "#585b70";
surface1 = "#45475a";
surface0 = "#313244";
base = "#1e1e2e";
mantle = "#181825";
crust = "#11111b";
};
in {
# Ensure bundled Waybar scripts are installed under ~/.config/waybar/scripts
home.file = builtins.listToAttrs (
map (name: {
name = ".config/waybar/scripts/" + name;
value = {
source = "${scriptsDir}/${name}";
executable = true;
};
})
scripts
);
programs.waybar = {
enable = true;
package = pkgs.waybar;
settings = [
{
layer = "top";
# mode = "dock"; # optional
exclusive = true;
passthrough = false;
position = "top";
spacing = 3;
"fixed-center" = true;
ipc = true;
"margin-top" = 3;
"margin-left" = 8;
"margin-right" = 8;
modules-left = [
"custom/separator#line"
"custom/startmenu"
"custom/qs_wallpapers_apply"
"custom/separator#blank"
"custom/qs_vid_wallpapers_apply"
"custom/separator#line"
"custom/separator#blank"
"custom/cava_mviz"
"custom/separator#blank"
"custom/separator#line"
"tray"
"custom/separator#line"
];
modules-center = [
"custom/separator#line"
"hyprland/workspaces#rw"
"custom/separator#line"
];
modules-right = [
"custom/separator#line"
"custom/swaync"
"custom/separator#line"
"idle_inhibitor"
"custom/separator#line"
"clock"
"custom/separator#line"
"custom/weather"
"custom/separator#line"
"group/audio"
"custom/separator#line"
"custom/power"
];
# ---------- Modules (merged from jak-waybar/Modules) ----------
temperature = {
interval = 10;
tooltip = true;
"hwmon-path" = [
"/sys/class/hwmon/hwmon1/temp1_input"
"/sys/class/thermal/thermal_zone0/temp"
];
"critical-threshold" = 82;
"format-critical" = "{temperatureC}°C {icon}";
format = "{temperatureC}°C {icon}";
"format-icons" = ["󰈸"];
"on-click-right" = "$HOME/.config/hypr/scripts/WaybarScripts.sh --nvtop";
};
# backlight module removed per request
battery = {
align = 0;
rotate = 0;
"full-at" = 100;
"design-capacity" = false;
states = {
good = 95;
warning = 30;
critical = 15;
};
format = "{icon} {capacity}%";
"format-charging" = " {capacity}%";
"format-plugged" = "󱘖 {capacity}%";
"format-alt-click" = "click";
"format-full" = "{icon} Full";
"format-alt" = "{icon} {time}";
"format-icons" = [
"󰂎"
"󰁺"
"󰁻"
"󰁼"
"󰁽"
"󰁾"
"󰁿"
"󰂀"
"󰂁"
"󰂂"
"󰁹"
];
"format-time" = "{H}h {M}min";
tooltip = true;
"tooltip-format" = "{timeTo} {power}w";
"on-click-middle" = "$HOME/.config/hypr/scripts/ChangeBlur.sh";
"on-click-right" = "$HOME/.config/hypr/scripts/Wlogout.sh";
};
bluetooth = {
format = " ";
"format-disabled" = "󰂳";
"format-connected" = "󰂱 {num_connections}";
"tooltip-format" = " {device_alias}";
"tooltip-format-connected" = "{device_enumerate}";
"tooltip-format-enumerate-connected" = " {device_alias} 󰂄{device_battery_percentage}%";
tooltip = true;
"on-click" = "blueman-manager";
};
clock = {
interval = 1;
format = " {:%I:%M %p}";
"format-alt" = " {:%H:%M %Y, %d %B, %A}";
"tooltip-format" = "<tt><small>{calendar}</small></tt>";
calendar = {
mode = "year";
"mode-mon-col" = 3;
"weeks-pos" = "right";
"on-scroll" = 1;
format = {
months = "<span color='#ffead3'><b>{}</b></span>";
days = "<span color='#ecc6d9'><b>{}</b></span>";
weeks = "<span color='#99ffdd'><b>W{:%V}</b></span>";
weekdays = "<span color='#ffcc66'><b>{}</b></span>";
today = "<span color='#ff6699'><b><u>{}</u></b></span>";
};
};
actions = {
"on-click-right" = "mode";
"on-click-forward" = "tz_up";
"on-click-backward" = "tz_down";
"on-scroll-up" = "shift_up";
"on-scroll-down" = "shift_down";
};
};
cpu = {
format = "{usage}% 󰍛";
interval = 1;
"min-length" = 5;
"format-alt-click" = "click";
"format-alt" = "{icon0}{icon1}{icon2}{icon3} {usage:>2}% 󰍛";
"format-icons" = [
""
""
""
""
""
""
""
""
];
"on-click-right" = "gnome-system-monitor";
};
disk = {
interval = 30;
path = "/";
format = "{percentage_used}% 󰋊";
"tooltip-format" = "{used} used out of {total} on {path} ({percentage_used}%)";
};
"hyprland/window" = {
format = "{}";
"max-length" = 25;
"separate-outputs" = true;
"offscreen-css" = true;
"offscreen-css-text" = "(inactive)";
rewrite = {
"(.*) Mozilla Firefox" = " $1";
"(.*) - fish" = "> [$1]";
"(.*) - zsh" = "> [$1]";
};
};
idle_inhibitor = {
tooltip = true;
"tooltip-format-activated" = "Idle_inhibitor active";
"tooltip-format-deactivated" = "Idle_inhibitor not active";
format = "{icon}";
# Requested change: coffee mug icon for both states
"format-icons" = {
activated = " ";
deactivated = " ";
};
};
"keyboard-state" = {
capslock = true;
format = {
numlock = "N {icon}";
capslock = "󰪛 {icon}";
};
"format-icons" = {
locked = "";
unlocked = "";
};
};
memory = {
interval = 10;
format = "{used:0.1f}G 󰾆";
"format-alt" = "{percentage}% 󰾆";
"format-alt-click" = "click";
tooltip = true;
"tooltip-format" = "{used:0.1f}GB/{total:0.1f}G";
"on-click-right" = "$HOME/.config/hypr/scripts/WaybarScripts.sh --btop";
};
network = {
format = "{ifname}";
"format-wifi" = "{icon}";
"format-ethernet" = "󰌘";
"format-disconnected" = "󰌙";
"tooltip-format" = "{ipaddr} {bandwidthUpBits} {bandwidthDownBits}";
"format-linked" = "󰈁 {ifname} (No IP)";
"tooltip-format-wifi" = "{essid} {icon} {signalStrength}%";
"tooltip-format-ethernet" = "{ifname} 󰌘";
"tooltip-format-disconnected" = "󰌙 Disconnected";
"max-length" = 30;
"format-icons" = [
"󰤯"
"󰤟"
"󰤢"
"󰤥"
"󰤨"
];
"on-click-right" = "$HOME/.config/hypr/scripts/WaybarScripts.sh --nmtui";
};
"network#speed" = {
interval = 1;
format = "{ifname}";
"format-wifi" = "{icon} {bandwidthUpBytes} {bandwidthDownBytes}";
"format-ethernet" = "󰌘 {bandwidthUpBytes} {bandwidthDownBytes}";
"format-disconnected" = "󰌙";
"tooltip-format" = "{ipaddr}";
"format-linked" = "󰈁 {ifname} (No IP)";
"tooltip-format-wifi" = "{essid} {icon} {signalStrength}%";
"tooltip-format-ethernet" = "{ifname} 󰌘";
"tooltip-format-disconnected" = "󰌙 Disconnected";
"min-length" = 24;
"max-length" = 24;
"format-icons" = [
"󰤯"
"󰤟"
"󰤢"
"󰤥"
"󰤨"
];
};
"power-profiles-daemon" = {
format = "{icon} ";
"tooltip-format" = "Power profile: {profile}\nDriver: {driver}";
tooltip = true;
"format-icons" = {
default = "";
performance = "";
balanced = "";
"power-saver" = "";
};
};
pulseaudio = {
format = "{icon} {volume}%";
"format-bluetooth" = "{icon} 󰂰 {volume}%";
"format-muted" = "󰖁";
"format-icons" = {
headphone = "";
"hands-free" = "";
headset = "";
phone = "";
portable = "";
car = "";
default = [
""
""
"󰕾"
""
];
"ignored-sinks" = ["Easy Effects Sink"];
};
"scroll-step" = 5.0;
"on-click" = "$HOME/.config/hypr/scripts/Volume.sh --toggle";
"on-click-right" = "pavucontrol -t 3";
"on-scroll-up" = "$HOME/.config/hypr/scripts/Volume.sh --inc";
"on-scroll-down" = "$HOME/.config/hypr/scripts/Volume.sh --dec";
"tooltip-format" = "{icon} {desc} | {volume}%";
"smooth-scrolling-threshold" = 1;
};
"pulseaudio#microphone" = {
format = "{format_source}";
"format-source" = " {volume}%";
"format-source-muted" = "";
"on-click" = "$HOME/.config/hypr/scripts/Volume.sh --toggle-mic";
"on-click-right" = "pavucontrol -t 4";
"on-scroll-up" = "$HOME/.config/hypr/scripts/Volume.sh --mic-inc";
"on-scroll-down" = "$HOME/.config/hypr/scripts/Volume.sh --mic-dec";
"tooltip-format" = "{source_desc} | {source_volume}%";
"scroll-step" = 5;
};
tray = {
"icon-size" = 20;
spacing = 4;
};
# ---------- Workspaces variants (from ModulesWorkspaces) ----------
"hyprland/workspaces#rw" = {
"disable-scroll" = true;
"all-outputs" = true;
"warp-on-scroll" = false;
"sort-by-number" = true;
"show-special" = false;
"on-click" = "activate";
"on-scroll-up" = "hyprctl dispatch workspace e+1";
"on-scroll-down" = "hyprctl dispatch workspace e-1";
"persistent-workspaces" = {
"*" = 5;
};
format = "{icon} {windows}";
"format-window-separator" = " ";
"window-rewrite-default" = " ";
"window-rewrite" = {
"title<.*amazon.*>" = " ";
"title<.*reddit.*>" = " ";
"title<.*[Hh]elium.*>" = " ";
"class<firefox|org.mozilla.firefox|librewolf|floorp|mercury-browser|[Cc]achy-browser>" = " ";
"class<zen>" = "󰰷 ";
"class<waterfox|waterfox-bin>" = " ";
"class<microsoft-edge>" = " ";
"class<Chromium|Thorium|[Cc]hrome>" = " ";
"class<helium>" = " ";
"class<brave-browser>" = "🦁 ";
"class<tor browser>" = " ";
"class<firefox-developer-edition>" = "🦊 ";
"class<kitty|konsole>" = " ";
"class<kitty-dropterm>" = " ";
"class<com.mitchellh.ghostty>" = " 󰊠";
"class<org.wezfurlong.wezterm>" = " ";
"class<Warp|warp|dev.warp.Warp|warp-terminal>" = "󰰭 ";
"class<[Tt]hunderbird|[Tt]hunderbird-esr>" = " ";
"class<eu.betterbird.Betterbird>" = " ";
"title<.*gmail.*>" = "󰊫 ";
"class<[Tt]elegram-desktop|org.telegram.desktop|io.github.tdesktop_x64.TDesktop>" = " ";
"class<discord|discord-canary|[Ww]ebcord|[Vv]esktop|com.discordapp.Discord|dev.vencord.Vesktop>" = " ";
"title<.*whatsapp.*>" = " ";
"title<.*zapzap.*>" = " ";
"title<.*messenger.*>" = " ";
"title<.*facebook.*>" = " ";
"class<[Ss]ignal|signal-desktop|org.signal.Signal>" = "󰍩 ";
"title<.*Signal.*>" = "󰍩 ";
"title<.*ChatGPT.*>" = "󰚩 ";
"title<.*deepseek.*>" = "󰚩 ";
"title<.*qwen.*>" = "󰚩 ";
"class<subl>" = "󰅳 ";
"class<slack>" = " ";
"class<mpv>" = " ";
"class<celluloid|Zoom>" = " ";
"class<Cider>" = "󰎆 ";
"title<.*Picture-in-Picture.*>" = " ";
"title<.*youtube.*>" = " ";
"class<vlc>" = "󰕼 ";
"class<[Kk]denlive|org.kde.kdenlive>" = "🎬 ";
"title<.*Kdenlive.*>" = "🎬 ";
"title<.*cmus.*>" = " ";
"class<[Ss]potify>" = " ";
"class<virt-manager>" = " ";
"class<.virt-manager-wrapped>" = " ";
"class<remote-viewer|virt-viewer>" = " ";
"class<virtualbox manager>" = "💽 ";
"title<virtualbox>" = "💽 ";
"class<remmina|org.remmina.Remmina>" = "🖥 ";
"class<VSCode|code|code-url-handler|code-oss|codium|codium-url-handler|VSCodium>" = "󰨞 ";
"class<dev.zed.Zed>" = "󰵁";
"class<codeblocks>" = "󰅩 ";
"title<.*github.*>" = " ";
"class<mousepad>" = " ";
"class<libreoffice-writer>" = " ";
"class<libreoffice-startcenter>" = "󰏆 ";
"class<libreoffice-calc>" = " ";
"title<.*nvim ~.*>" = " ";
"title<.*vim.*>" = " ";
"title<.*nvim.*>" = " ";
"title<.*Discord.*>" = " ";
"title<.*figma.*>" = " ";
"title<.*jira.*>" = " ";
"class<jetbrains-idea>" = " ";
"class<obs|com.obsproject.Studio>" = " ";
"class<polkit-gnome-authentication-agent-1>" = "󰒃 ";
"class<nwg-look>" = " ";
"class<[Pp]avucontrol|org.pulseaudio.pavucontrol>" = "󱡫 ";
"class<steam>" = " ";
"class<thunar|nemo>" = "󰝰 ";
"class<Gparted>" = "";
"class<gimp>" = " ";
"class<emulator>" = "📱 ";
"class<android-studio>" = " ";
"class<org.pipewire.Helvum>" = "󰓃";
"class<localsend>" = "";
"class<PrusaSlicer|UltiMaker-Cura|OrcaSlicer>" = "󰹛";
"class<io.github.kolunmi.Bazaar>" = " ";
"title<^Bazaar$>" = " ";
"class<com.gabm.satty>" = " ";
"title<^satty$>" = " ";
"class<[Bb]ox[Bb]uddy|io.github.dvlv.boxbuddy|io.github.dvlv.BoxBuddy>" = " ";
"title<.*BoxBuddy.*>" = " ";
# qs-* apps
"title<Hyprland Keybinds>" = " ";
"title<Niri Keybinds>" = " ";
"title<BSPWM Keybinds>" = " ";
"title<DWM Keybinds>" = " ";
"title<Emacs Leader Keybinds>" = " ";
"title<Kitty Configuration>" = " ";
"title<WezTerm Configuration>" = " ";
"title<Yazi Configuration>" = " ";
"title<Cheatsheets Viewer>" = " ";
"title<Documentation Viewer>" = " ";
"title<^Wallpapers$>" = " ";
"title<^Video Wallpapers$>" = " ";
"title<^qs-wlogout$>" = " ";
};
};
# ---------- Groups (from ModulesGroups) ----------
"group/app_drawer" = {
orientation = "inherit";
drawer = {
"transition-duration" = 500;
"children-class" = "custom/menu";
"transition-left-to-right" = true;
};
modules = [
"custom/menu"
"custom/file_manager"
"custom/tty"
"custom/browser"
];
};
"group/mobo_drawer" = {
orientation = "inherit";
drawer = {
"transition-duration" = 500;
"children-class" = "cpu";
"transition-left-to-right" = true;
};
modules = [
"temperature"
"cpu"
"power-profiles-daemon"
"memory"
"disk"
];
};
"group/laptop" = {
orientation = "inherit";
modules = ["battery"];
};
"group/audio" = {
orientation = "inherit";
drawer = {
"transition-duration" = 500;
"children-class" = "pulseaudio";
"transition-left-to-right" = true;
};
modules = [
"pulseaudio"
"pulseaudio#microphone"
];
};
"group/status" = {
orientation = "inherit";
drawer = {
"transition-duration" = 500;
"children-class" = "custom/power";
"transition-left-to-right" = false;
};
modules = [
"custom/power"
"custom/lock"
"keyboard-state"
];
};
# ---------- Custom modules (from ModulesCustom) ----------
"custom/weather" = {
return-type = "json";
exec = "sh -lc 'WEATHER_ICON_STYLE=emoji WEATHER_TOOLTIP_MARKUP=1 ~/.local/bin/weather'";
interval = 600;
tooltip = true;
};
"custom/qs_wallpapers_apply" = {
# Image wallpaper apply (qs-wallpapers-apply)
format = " ";
"on-click" = "qs-wallpapers-apply";
tooltip = true;
"tooltip-format" = "Set wallpaper";
};
"custom/qs_vid_wallpapers_apply" = {
# Video wallpaper apply (qs-vid-wallpapers-apply)
format = " ";
"on-click" = "qs-vid-wallpapers-apply";
tooltip = true;
"tooltip-format" = "Set video wallpaper";
};
"custom/hypridle" = {
format = "󱫗 ";
"return-type" = "json";
escape = true;
"exec-on-event" = true;
interval = 60;
exec = "$HOME/.config/hypr/scripts/Hypridle.sh status";
"on-click" = "$HOME/.config/hypr/scripts/Hypridle.sh toggle";
"on-click-right" = "hyprlock";
};
"custom/lock" = {
format = "󰌾";
"on-click" = "$HOME/.config/hypr/scripts/LockScreen.sh";
tooltip = true;
"tooltip-format" = "󰷛 Screen Lock";
};
"custom/startmenu" = {
tooltip = true;
"tooltip-format" = "App menu";
format = "";
# on-click = "rofi -show drun";
on-click = "launch-nwg-menu";
"on-click-right" = "nwg-drawer -mr 225 -ml 225 -mt 200 -mb 200 -is 48 --spacing 15";
};
# Integrated CAVA visualizer using the inline script above
"custom/cava_mviz" = {
exec = "${waybarCava}/bin/WaybarCava";
format = "<span color='#a6e3a1'>[</span> {} <span color='#a6e3a1'>]</span>";
};
"custom/playerctl" = {
format = "<span>{}</span>";
"return-type" = "json";
"max-length" = 25;
exec = "playerctl -a metadata --format '{\"text\": \"{{artist}} {{markup_escape(title)}}\", \"tooltip\": \"{{playerName}} : {{markup_escape(title)}}\", \"alt\": \"{{status}}\", \"class\": \"{{status}}\"}' -F";
"on-click-middle" = "playerctl play-pause";
"on-click" = "playerctl previous";
"on-click-right" = "playerctl next";
"scroll-step" = 5.0;
"on-scroll-up" = "$HOME/.config/hypr/scripts/Volume.sh --inc";
"on-scroll-down" = "$HOME/.config/hypr/scripts/Volume.sh --dec";
"smooth-scrolling-threshold" = 1;
};
"custom/power" = {
format = " ";
"on-click" = "qs-wlogout";
"on-click-right" = "~/.config/waybar/scripts/power-menu.sh";
tooltip = true;
"tooltip-format" = "Power menu: Left-click for QS logout, Right-click for rofi power menu";
};
"custom/reboot" = {
format = "󰜉";
"on-click" = "systemctl reboot";
tooltip = true;
"tooltip-format" = "Left Click: Reboot";
};
"custom/quit" = {
format = "󰗼";
"on-click" = "hyprctl dispatch exit";
tooltip = true;
"tooltip-format" = "Left Click: Exit Hyprland";
};
"custom/swaync" = {
tooltip = true;
"tooltip-format" = "Left Click: Launch Notification Center\nRight Click: Do not Disturb";
format = "{} {icon} ";
"format-icons" = {
notification = "<span foreground='red'><sup></sup></span>";
none = "";
"dnd-notification" = "<span foreground='red'><sup></sup></span>";
"dnd-none" = "";
"inhibited-notification" = "<span foreground='red'><sup></sup></span>";
"inhibited-none" = "";
"dnd-inhibited-notification" = "<span foreground='red'><sup></sup></span>";
"dnd-inhibited-none" = "";
};
"return-type" = "json";
"exec-if" = "which swaync-client";
exec = "swaync-client -swb";
"on-click" = "systemctl --user start swaync.service; swaync-client -t";
"on-click-right" = "systemctl --user start swaync.service; swaync-client -d";
escape = true;
};
# Separators
"custom/separator#dot" = {
format = "";
interval = "once";
tooltip = false;
};
"custom/separator#dot-line" = {
format = "";
interval = "once";
tooltip = false;
};
"custom/separator#line" = {
format = "|";
interval = "once";
tooltip = false;
};
"custom/separator#blank" = {
format = "";
interval = "once";
tooltip = false;
};
"custom/separator#blank_2" = {
format = " ";
interval = "once";
tooltip = false;
};
"custom/separator#blank_3" = {
format = " ";
interval = "once";
tooltip = false;
};
}
];
# Consolidated style (Catppuccin Mocha) inlined
style = let
c = catppuccinColors;
in ''
@define-color rosewater ${c.rosewater};
@define-color flamingo ${c.flamingo};
@define-color pink ${c.pink};
@define-color mauve ${c.mauve};
@define-color red ${c.red};
@define-color maroon ${c.maroon};
@define-color peach ${c.peach};
@define-color yellow ${c.yellow};
@define-color green ${c.green};
@define-color teal ${c.teal};
@define-color sky ${c.sky};
@define-color sapphire ${c.sapphire};
@define-color blue ${c.blue};
@define-color lavender ${c.lavender};
@define-color text ${c.text};
@define-color subtext1 ${c.subtext1};
@define-color subtext0 ${c.subtext0};
@define-color overlay2 ${c.overlay2};
@define-color overlay1 ${c.overlay1};
@define-color overlay0 ${c.overlay0};
@define-color surface2 ${c.surface2};
@define-color surface1 ${c.surface1};
@define-color surface0 ${c.surface0};
@define-color base ${c.base};
@define-color mantle ${c.mantle};
@define-color crust ${c.crust};
* {
font-family: "JetBrainsMono Nerd Font";
font-weight: bold;
min-height: 0;
font-size: 101%;
font-feature-settings: '"zero", "ss01", "ss02", "ss03", "ss04", "ss05", "cv31"';
}
window#waybar {
background-color: @base;
border-radius: 5px;
}
tooltip {
background: @base;
opacity: 1;
border-radius: 10px;
border-width: 2px;
border-style: solid;
border-color: @sapphire;
}
tooltip label { color: @blue; }
/* Extra spacing between NixOS start menu and CAVA */
#custom-startmenu { margin-left: 4px; margin-right: 8px; }
#custom-cava_mviz { margin-left: 4px; }
#taskbar button, #workspaces button {
color: @surface2;
background-color: transparent;
padding-top: 4px;
padding-bottom: 4px;
padding-right: 6px;
padding-left: 4px;
}
#taskbar button.active { color: @maroon; }
#workspaces button.active { color: @green; }
#taskbar button.focused, #workspaces button.focused {
color: @rosewater;
background: transparent;
border-radius: 15px;
}
#workspaces button.urgent {
color: #11111b;
background: transparent;
border-radius: 15px;
}
#taskbar button:hover, #workspaces button:hover {
background: transparent;
color: @flamingo;
border-radius: 15px;
}
/* Workspaces colors: inactive= @sapphire (pale blue), active= @green, empty= @red */
#workspaces button { color: @sapphire; }
#workspaces button.empty { color: @red; }
#backlight,
#backlight-slider,
#battery,
#bluetooth,
#clock,
#cpu,
#disk,
#idle_inhibitor,
#keyboard-state,
#memory,
#mode,
#mpris,
#network,
#power-profiles-daemon,
#pulseaudio,
#pulseaudio-slider,
#taskbar button,
#taskbar,
#temperature,
#tray,
#window,
#wireplumber,
#workspaces,
#custom-backlight,
#custom-browser,
#custom-cava_mviz,
#custom-cycle_wall,
#custom-dot_update,
#custom-file_manager,
#custom-keybinds,
#custom-keyboard,
#custom-light_dark,
#custom-lock,
#custom-hint,
#custom-hypridle,
#custom-menu,
#custom-playerctl,
#custom-power_vertical,
#custom-power,
#custom-quit,
#custom-reboot,
#custom-settings,
#custom-spotify,
#custom-swaync,
#custom-tty,
#custom-updater,
#custom-weather,
#custom-weather.clearNight,
#custom-weather.cloudyFoggyDay,
#custom-weather.cloudyFoggyNight,
#custom-weather.default,
#custom-weather.rainyDay,
#custom-weather.rainyNight,
#custom-weather.severe,
#custom-weather.showyIcyDay,
#custom-weather.snowyIcyNight,
#custom-weather.sunnyDay {
opacity: 1;
padding-top: 4px;
padding-bottom: 4px;
padding-right: 6px;
padding-left: 6px;
}
#idle_inhibitor.activated { color: @green; }
#idle_inhibitor.deactivated { color: @red; }
#mpris { color: @rosewater; }
#battery { color: @green; padding-left: 15px; border-radius: 15px 0 0 15px; }
@keyframes blink { to { background-color: #ffffff; color: #333333; } }
#battery.critical:not(.charging) {
color: #f53c3c;
animation-name: blink;
animation-duration: 0.5s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-direction: alternate;
}
#custom-lock, #custom-power { color: @red; border-radius: 15px; font-weight: bolder; padding-left: 1px; }
#network { background-color: transparent; color: @mauve; }
#backlight { color: @flamingo; }
#custom-weather { color: @green; border-radius: 15px; background-color: transparent; }
#custom-menu { color: #89b4fa; }
#pulseaudio { background-color: transparent; color: @blue; }
#clock, #clock-calender { color: @green; }
/* Use the same pale blue as clock for these icons */
#custom-qs_wallpapers_apply,
#custom-qs_vid_wallpapers_apply { color: @sapphire; }
/* Focused window title */
#window { color: @lavender; }
/* When offscreen (offscreen-css = true) */
#window.offscreen { color: @overlay1; }
/* Subtle hover effect */
#window:hover { color: @rosewater; transition: color 120ms ease-in-out; }
/* Playerctl track text */
#custom-playerctl { color: @lavender; }
#custom-playerctl:hover { color: @rosewater; transition: color 120ms ease-in-out; }
/* Start menu and notifications in green; turn red on new alerts */
#custom-startmenu { color: @green; }
#custom-swaync { color: @green; }
#custom-swaync.notification,
#custom-swaync.dnd-notification,
#custom-swaync.inhibited-notification { color: @red; }
/* Group drawer button color */
#group-mobo_drawer { color: @green; }
#backlight-slider slider, #pulseaudio-slider slider {
min-height: 7px; min-width: 15px; opacity: 0; background-color: @text; border-radius: 3px; box-shadow: 1px 5px 6px 1px #272727;
}
#backlight-slider trough, #pulseaudio-slider trough {
min-height: 100px; min-width: 7px; border-radius: 5px; background-color: @surface0;
}
#backlight-slider highlight, #pulseaudio-slider highlight { min-width: 5px; border-radius: 5px; background-color: @blue; }
'';
};
}

View File

@@ -0,0 +1,846 @@
{
pkgs,
lib,
...
}:
with lib; let
# Install Waybar helper scripts under ~/.config/waybar/scripts (needed for power-menu.sh)
scriptsDir = ./scripts;
scripts = builtins.attrNames (builtins.readDir scriptsDir);
# Import upstream wallust color variables and base style from the source theme
wallustColors = ''
/* ---- 💫 https://github.com/JaKooLit 💫 ---- */
/* wallust template - colors-waybar */
@define-color foreground #DADDF8;
@define-color background #1A1921;
@define-color background-alt rgba(26,25,33,0.25);
@define-color cursor #7B7EA0;
@define-color color0 #413F48;
@define-color color1 #0F102E;
@define-color color2 #151636;
@define-color color3 #3F3683;
@define-color color4 #404470;
@define-color color5 #646AA1;
@define-color color6 #7177B1;
@define-color color7 #C2C7ED;
@define-color color8 #888BA6;
@define-color color9 #13153E;
@define-color color10 #1C1E49;
@define-color color11 #5448AF;
@define-color color12 #555A95;
@define-color color13 #868ED6;
@define-color color14 #969FEC;
@define-color color15 #C2C7ED;
'';
baseStyle = ''
/* ---- 💫 https://github.com/JaKooLit 💫 ---- */
/* Oglo Chicklets */
* {
font-family: "JetBrainsMono Nerd Font", FontAwesome, Roboto, Helvetica, Arial, sans-serif;
font-size: 97%;
font-weight: bold;
}
window#waybar {
background-color: #232a2e;
border-bottom: 8px solid #1d2327;
color: #d3c6aa;
transition-property: background-color;
transition-duration: .5s;
}
window#waybar.hidden {
opacity: 0.2;
}
/*
window#waybar.empty {
background-color: transparent;
}
window#waybar.solo {
background-color: #FFFFFF;
}
*/
button {
all: unset;
background-color: #778f52;
color: #2d353b;
border: none;
border-bottom: 8px solid #5d743e;
border-radius: 5px;
padding-left: 15px;
padding-right: 15px;
transition: transform 0.1s ease-in-out;
}
button:hover {
background: inherit;
background-color: #92ab6c;
border-bottom: 8px solid #788f57;
}
button.active {
background: inherit;
background-color: #a5be7e;
border-bottom: 8px solid #8aa168;
}
#mode {
background-color: #64727D;
border-bottom: 3px solid #ffffff;
}
#backlight,
#backlight-slider,
#battery,
#bluetooth,
#clock,
#cpu,
#disk,
#idle_inhibitor,
#keyboard-state,
#memory,
#mode,
#mpris,
#network,
#power-profiles-daemon,
#pulseaudio,
#pulseaudio-slider,
#taskbar,
#temperature,
#tray,
#window,
#wireplumber,
#workspaces,
#custom-backlight,
#custom-browser,
#custom-cava_mviz,
#custom-cycle_wall,
#custom-dot_update,
#custom-file_manager,
#custom-keybinds,
#custom-keyboard,
#custom-light_dark,
#custom-lock,
#custom-hint,
#custom-hypridle,
#custom-menu,
#custom-playerctl,
#custom-power_vertical,
#custom-power,
#custom-quit,
#custom-reboot,
#custom-settings,
#custom-spotify,
#custom-swaync,
#custom-tty,
#custom-updater,
#custom-hyprpicker,
#custom-weather,
#custom-weather.clearNight,
#custom-weather.cloudyFoggyDay,
#custom-weather.cloudyFoggyNight,
#custom-weather.default,
#custom-weather.rainyDay,
#custom-weather.rainyNight,
#custom-weather.severe,
#custom-weather.showyIcyDay,
#custom-weather.snowyIcyNight,
#custom-weather.sunnyDay{
color: #ffffff;
padding-top: 2px;
padding-bottom: 2px;
border-radius: 5px;
padding-left: 5px;
padding-right: 5px;
}
#window,
#workspaces {
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 {
background-color: #343f44;
color: #d3c6aa;
border-bottom: 8px solid #2b3539;
}
#custom-swaync {
background-color: #778f52;
color: #2d353b;
border-bottom: 8px solid #5d743e;
}
#custom-menu {
background-color: #778f52;
color: #2d353b;
border-bottom: 8px solid #5d743e;
}
#custom-power {
background-color: #ee606a;
color: #2d353b;
border-bottom: 8px solid #ca4853;
padding-left: 10px;
}
#custom-power_vertical{
background-color: #ee606a;
color: #2d353b;
border-bottom: 8px solid #ca4853;
}
#clock {
background-color: #96a84c;
color: #2d353b;
border-bottom: 8px solid #7a8c37;
}
#battery {
background-color: #3a998f;
color: #2d353b;
border-bottom: 8px solid #227d74;
}
@keyframes blink {
to {
background-color: #ffffff;
color: #000000;
}
}
#battery.critical:not(.charging) {
background-color: #ee606a;
color: #2d353b;
border-bottom: 8px solid #ca4853;
animation-name: blink;
animation-duration: 3.0s;
animation-timing-function: steps(12);
animation-iteration-count: infinite;
animation-direction: alternate;
}
label:focus {
background-color: #000000;
}
#cpu {
background-color: #778f52;
color: #2d353b;
border-bottom: 8px solid #5d743e;
}
#memory {
background-color: #d980ad;
color: #2d353b;
border-bottom: 8px solid #b86790;
}
#disk {
background-color: #964B00;
border-bottom: 8px solid #793300;
}
#custom-lock,
#custom-light_dark,
#backlight {
background-color: #64b6ac;
color: #2d353b;
border-bottom: 8px solid #4f9990;
padding-left: 10px;
}
#network {
background-color: #2980b9;
}
#network.disconnected {
background-color: #f53c3c;
}
#pulseaudio {
background-color: #d8ac47;
color: #2d353b;
border-bottom: 8px solid #b78f30;
}
#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;
}
#temperature {
background-color: #f0932b;
border-bottom: 8px solid #b78f30;
}
#temperature.critical {
background-color: #eb4d4b;
}
#tray {
background-color: #e67f51;
color: #2d353b;;
border-bottom: 8px solid #c3653b;
}
#tray > .passive {
-gtk-icon-effect: dim;
}
#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;
min-width: 16px;
}
#keyboard-state {
background: #97e1ad;
color: #000000;
min-width: 16px;
border-bottom: 8px solid #78b48a;
}
#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;
}
tooltip {
background-color: #232a2e;
border: none;
border-bottom: 8px solid #1d2327;
}
tooltip decoration {
box-shadow: none;
}
tooltip decoration:backdrop {
box-shadow: none;
}
tooltip label {
color: #d3c6aa;
padding-left: 5px;
padding-right: 5px;
padding-top: 0px;
padding-bottom: 5px;
}
#pulseaudio-slider slider {
min-width: 0px;
min-height: 0px;
opacity: 0;
background-image: none;
border: none;
box-shadow: none;
}
#pulseaudio-slider trough {
background-color: #7f849c;
min-width: 80px;
min-height: 5px;
border-radius: 5px;
}
#pulseaudio-slider highlight {
min-height: 10px;
border-radius: 5px;
}
#backlight-slider slider {
min-width: 0px;
min-height: 0px;
opacity: 0;
background-image: none;
border: none;
box-shadow: none;
}
#backlight-slider trough {
background-color: #7f849c;
min-width: 80px;
min-height: 10px;
border-radius: 5px;
}
#backlight-slider highlight {
min-width: 10px;
border-radius: 5px;
}
'';
# Explicit overrides for notification coffee mug colors (red when inactive/no notifications, green when active/has notifications)
overrides = ''
/* Coffee mug notification colors */
#custom-swaync { color: #ee606a; } /* default red */
#custom-swaync.none,
#custom-swaync.dnd-none,
#custom-swaync.inhibited-none { color: #ee606a; }
#custom-swaync.notification,
#custom-swaync.dnd-notification,
#custom-swaync.inhibited-notification { color: #00f769; }
'';
in {
# Install scripts (power-menu.sh, etc.)
home.file = builtins.listToAttrs (
map (name: {
name = ".config/waybar/scripts/" + name;
value = {
source = "${scriptsDir}/${name}";
executable = true;
};
})
scripts
);
programs.waybar = {
enable = true;
package = pkgs.waybar;
settings = [
{
layer = "top";
position = "top";
# mode = "dock";
exclusive = true;
passthrough = false;
"gtk-layer-shell" = true;
"margin-left" = 6;
"margin-right" = 6;
"margin-top" = 2;
# Layout
"modules-left" = [
"idle_inhibitor"
"group/mobo_drawer"
"hyprland/workspaces#rw"
"tray"
"mpris"
];
"modules-center" = [
"clock#2"
"group/notify"
"custom/weather"
];
"modules-right" = [
"hyprland/window"
"battery"
"group/audio"
"custom/power"
];
# Module definitions (subset needed by this layout)
battery = {
align = 0;
rotate = 0;
"full-at" = 100;
"design-capacity" = false;
states = {
good = 95;
warning = 30;
critical = 15;
};
format = "{icon} {capacity}%";
"format-charging" = " {capacity}%";
"format-plugged" = "󱘖 {capacity}%";
"format-alt-click" = "click";
"format-full" = "{icon} Full";
"format-alt" = "{icon} {time}";
"format-icons" = [
"󰂎"
"󰁺"
"󰁻"
"󰁼"
"󰁽"
"󰁾"
"󰁿"
"󰂀"
"󰂁"
"󰂂"
"󰁹"
];
"format-time" = "{H}h {M}min";
tooltip = true;
"tooltip-format" = "{timeTo} {power}w";
"on-click-middle" = "$HOME/.config/hypr/scripts/ChangeBlur.sh";
"on-click-right" = "$HOME/.config/hypr/scripts/Wlogout.sh";
};
"clock#2" = {
format = " {:%I:%M %p}";
"format-alt" = "{:%A | %H:%M | %e %B}";
"tooltip-format" = "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>";
};
cpu = {
format = "{usage}% 󰍛";
interval = 1;
"min-length" = 5;
"format-alt-click" = "click";
"format-alt" = "{icon0}{icon1}{icon2}{icon3} {usage:>2}% 󰍛";
"format-icons" = ["" "" "" "" "" "" "" ""];
"on-click-right" = "gnome-system-monitor";
};
disk = {
interval = 30;
path = "/";
format = "{percentage_used}% 󰋊";
"tooltip-format" = "{used} used out of {total} on {path} ({percentage_used}%)";
};
"hyprland/workspaces#rw" = {
"disable-scroll" = true;
"all-outputs" = true;
"warp-on-scroll" = false;
"sort-by-number" = true;
"show-special" = false;
"on-click" = "activate";
"on-scroll-up" = "hyprctl dispatch workspace e+1";
"on-scroll-down" = "hyprctl dispatch workspace e-1";
"persistent-workspaces" = {"*" = 5;};
format = "{icon} {windows}";
"format-window-separator" = " ";
"window-rewrite-default" = " ";
"window-rewrite" = {
"(.*) Mozilla Firefox" = " $1";
"(.*) - fish" = "> [$1]";
"(.*) - zsh" = "> [$1]";
"class<Warp|warp|dev.warp.Warp|warp-terminal>" = "󰰭 ";
"class<com.mitchellh.ghostty>" = " 󰊠";
"class<remote-viewer|virt-viewer>" = " ";
"class<helium>" = " ";
"class<[Ss]ignal|signal-desktop|org.signal.Signal>" = "󰍩 ";
"title<.*Signal.*>" = "󰍩 ";
"class<remmina|org.remmina.Remmina>" = "🖥 ";
"class<[Kk]denlive|org.kde.kdenlive>" = "🎬 ";
"title<.*Kdenlive.*>" = "🎬 ";
# qs-* apps
"title<Hyprland Keybinds>" = " ";
"title<Niri Keybinds>" = " ";
"title<BSPWM Keybinds>" = " ";
"title<DWM Keybinds>" = " ";
"title<Emacs Leader Keybinds>" = " ";
"title<Kitty Configuration>" = " ";
"title<WezTerm Configuration>" = " ";
"title<Yazi Configuration>" = " ";
"title<Cheatsheets Viewer>" = " ";
"title<Documentation Viewer>" = " ";
"title<^Wallpapers$>" = " ";
"title<^Video Wallpapers$>" = " ";
"title<^qs-wlogout$>" = " ";
# BoxBuddy
"class<[Bb]ox[Bb]uddy|io.github.dvlv.boxbuddy|io.github.dvlv.BoxBuddy>" = " ";
"title<.*BoxBuddy.*>" = " ";
# Bazaar software store
"class<io.github.kolunmi.Bazaar>" = " ";
"title<^Bazaar$>" = " ";
# satty screenshot tool
"class<com.gabm.satty>" = " ";
"title<^satty$>" = " ";
};
};
"hyprland/window" = {
format = "{}";
"max-length" = 25;
"separate-outputs" = true;
"offscreen-css" = true;
"offscreen-css-text" = "(inactive)";
};
idle_inhibitor = {
tooltip = true;
"tooltip-format-activated" = "Idle_inhibitor active";
"tooltip-format-deactivated" = "Idle_inhibitor not active";
format = "{icon}";
"format-icons" = {
activated = " ";
deactivated = " ";
};
};
memory = {
interval = 10;
format = "{used:0.1f}G 󰾆";
"format-alt" = "{percentage}% 󰾆";
"format-alt-click" = "click";
tooltip = true;
"tooltip-format" = "{used:0.1f}GB/{total:0.1f}G";
"on-click-right" = "$HOME/.config/hypr/scripts/WaybarScripts.sh --btop";
};
"custom/weather" = {
"return-type" = "json";
exec = "sh -lc 'WEATHER_ICON_STYLE=emoji WEATHER_TOOLTIP_MARKUP=1 ~/.local/bin/weather'";
interval = 600;
tooltip = true;
};
mpris = {
interval = 10;
format = "{player_icon} ";
"format-paused" = "{status_icon} <i>{dynamic}</i>";
"on-click-middle" = "playerctl play-pause";
"on-click" = "playerctl previous";
"on-click-right" = "playerctl next";
"scroll-step" = 5.0;
"on-scroll-up" = "$HOME/.config/hypr/scripts/Volume.sh --inc";
"on-scroll-down" = "$HOME/.config/hypr/scripts/Volume.sh --dec";
"smooth-scrolling-threshold" = 1;
tooltip = true;
"tooltip-format" = "{status_icon} {dynamic}\nLeft Click: previous\nMid Click: Pause\nRight Click: Next";
"player-icons" = {
chromium = "";
default = "";
firefox = "";
kdeconnect = "";
mopidy = "";
mpv = "󰐹";
spotify = "";
vlc = "󰕼";
};
"status-icons" = {
paused = "󰐎";
playing = "";
stopped = "";
};
"max-length" = 30;
};
pulseaudio = {
format = "{icon} {volume}%";
"format-bluetooth" = "{icon} 󰂰 {volume}%";
"format-muted" = "󰖁";
"format-icons" = {
headphone = "";
"hands-free" = "";
headset = "";
phone = "";
portable = "";
car = "";
default = ["" "" "󰕾" ""];
"ignored-sinks" = ["Easy Effects Sink"];
};
"scroll-step" = 5.0;
"on-click" = "$HOME/.config/hypr/scripts/Volume.sh --toggle";
"on-click-right" = "pavucontrol -t 3";
"on-scroll-up" = "$HOME/.config/hypr/scripts/Volume.sh --inc";
"on-scroll-down" = "$HOME/.config/hypr/scripts/Volume.sh --dec";
"tooltip-format" = "{icon} {desc} | {volume}%";
"smooth-scrolling-threshold" = 1;
};
"pulseaudio#microphone" = {
format = "{format_source}";
"format-source" = " {volume}%";
"format-source-muted" = "";
"on-click" = "$HOME/.config/hypr/scripts/Volume.sh --toggle-mic";
"on-click-right" = "pavucontrol -t 4";
"on-scroll-up" = "$HOME/.config/hypr/scripts/Volume.sh --mic-inc";
"on-scroll-down" = "$HOME/.config/hypr/scripts/Volume.sh --mic-dec";
"tooltip-format" = "{source_desc} | {source_volume}%";
"scroll-step" = 5;
};
tray = {
"icon-size" = 20;
spacing = 4;
};
# Groups
"group/mobo_drawer" = {
orientation = "inherit";
drawer = {
"transition-duration" = 500;
"children-class" = "cpu";
"transition-left-to-right" = true;
};
modules = ["temperature" "cpu" "power-profiles-daemon" "memory" "disk"];
};
"group/audio" = {
orientation = "inherit";
drawer = {
"transition-duration" = 500;
"children-class" = "pulseaudio";
"transition-left-to-right" = true;
};
modules = ["pulseaudio" "pulseaudio#microphone"];
};
"group/notify" = {
orientation = "inherit";
drawer = {
"transition-duration" = 500;
"children-class" = "custom/swaync";
"transition-left-to-right" = false;
};
modules = ["custom/swaync" "custom/dot_update"];
};
# Custom modules
"custom/power" = {
format = " ";
"on-click" = "qs-wlogout";
"on-click-right" = "~/.config/waybar/scripts/power-menu.sh";
tooltip = true;
"tooltip-format" = "Power menu: Left-click for QS logout, Right-click for rofi power menu";
};
"custom/quit" = {
format = "󰗼";
"on-click" = "hyprctl dispatch exit";
tooltip = true;
"tooltip-format" = "Left Click: Exit Hyprland";
};
"custom/swaync" = {
tooltip = true;
"tooltip-format" = "Left Click: Launch Notification Center\nRight Click: Do not Disturb";
format = "{} {icon} ";
# Coffee mug icon for all states
"format-icons" = {
notification = "";
none = "";
"dnd-notification" = "";
"dnd-none" = "";
"inhibited-notification" = "";
"inhibited-none" = "";
"dnd-inhibited-notification" = "";
"dnd-inhibited-none" = "";
};
"return-type" = "json";
"exec-if" = "which swaync-client";
exec = "swaync-client -swb";
"on-click" = "sleep 0.1 && swaync-client -t -sw";
"on-click-right" = "swaync-client -d -sw";
escape = true;
};
# Support modules referenced by groups
temperature = {
interval = 10;
tooltip = true;
"hwmon-path" = ["/sys/class/hwmon/hwmon1/temp1_input" "/sys/class/thermal/thermal_zone0/temp"];
"critical-threshold" = 82;
"format-critical" = "{temperatureC}°C {icon}";
format = "{temperatureC}°C {icon}";
"format-icons" = ["󰈸"];
"on-click-right" = "$HOME/.config/hypr/scripts/WaybarScripts.sh --nvtop";
};
"power-profiles-daemon" = {
format = "{icon} ";
"tooltip-format" = "Power profile: {profile}\nDriver: {driver}";
tooltip = true;
"format-icons" = {
default = "";
performance = "";
balanced = "";
"power-saver" = "";
};
};
"custom/dot_update" = {
format = " 󰁈 ";
"on-click" = "$HOME/.config/hypr/scripts/KooLsDotsUpdate.sh";
tooltip = true;
"tooltip-format" = "Check KooL Dots update\nIf available";
};
}
];
# Compose style: wallust colors first, then base style, then our overrides
style = wallustColors + baseStyle + overrides;
};
}

View File

@@ -0,0 +1,356 @@
{
pkgs,
config,
lib,
...
}: let
terminal = "kitty";
base00 = "0F1419";
base01 = "131721";
base03 = "3E4B59";
base05 = "E6E1CF";
base06 = "E6E1CF";
base07 = "F3F4F5";
base08 = "F07178";
base09 = "FF8F40";
base0A = "FFB454";
base0B = "B8CC52";
base0C = "95E6CB";
base0D = "59C2FF";
base0E = "D2A6FF";
base0F = "E6B673";
in
with lib; {
# Configure & Theme Waybar
programs.waybar = {
enable = true;
package = pkgs.waybar;
settings = [
{
layer = "top";
position = "top";
modules-center = ["network" "pulseaudio" "cpu" "hyprland/workspaces" "memory" "disk" "clock"]; # Eterna: [ "hyprland/window" ]
modules-left = ["custom/startmenu" "hyprland/window"]; # Eternal: [ "hyprland/workspaces" "cpu" "memory" "network" ]
modules-right = ["tray" "idle_inhibitor" "custom/notification" "battery" "custom/exit"]; # Eternal: [ "idle_inhibitor" "pulseaudio" "clock" "custom/notification" "tray" ]
"hyprland/workspaces" = {
format = "{name}";
format-icons = {
default = " ";
active = " ";
urgent = " ";
};
on-scroll-up = "hyprctl dispatch workspace e+1";
on-scroll-down = "hyprctl dispatch workspace e-1";
};
"clock" = {
format = '' {:%H:%M}'';
/*
''{: %I:%M %p}'';
*/
tooltip = true;
tooltip-format = "<big>{:%A, %d.%B %Y }</big><tt><small>{calendar}</small></tt>";
};
"hyprland/window" = {
max-length = 60;
separate-outputs = false;
};
"memory" = {
interval = 5;
format = " {}%";
tooltip = true;
on-click = "${terminal} -e btop";
};
"cpu" = {
interval = 5;
format = " {usage:2}%";
tooltip = true;
on-click = "${terminal} -e btop";
};
"disk" = {
format = " {free}";
tooltip = true;
# Not working with zaneyos window open then closes
#on-click = "${terminal} -e sh -c df -h ; read";
};
"network" = {
format-icons = ["󰤯" "󰤟" "󰤢" "󰤥" "󰤨"];
format-ethernet = " {bandwidthDownBits}";
format-wifi = " {bandwidthDownBits}";
format-disconnected = "󰤮";
tooltip = false;
on-click = "${terminal} -e btop";
};
"tray" = {
spacing = 12;
};
"pulseaudio" = {
format = "{icon} {volume}% {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-icons = {
headphone = "";
hands-free = "";
headset = "";
phone = "";
portable = "";
car = "";
default = ["" "" ""];
};
on-click = "pavucontrol";
};
"custom/exit" = {
tooltip = false;
format = "";
on-click = "sleep 0.1 && wlogout";
};
"custom/startmenu" = {
tooltip = false;
format = " ";
# exec = "rofi -show drun";
on-click = "rofi -show drun";
};
"idle_inhibitor" = {
format = "{icon}";
format-icons = {
activated = " ";
deactivated = " ";
};
tooltip = "true";
};
"custom/notification" = {
tooltip = false;
format = "{icon} {}";
format-icons = {
notification = "<span foreground='red'><sup></sup></span>";
none = "";
dnd-notification = "<span foreground='red'><sup></sup></span>";
dnd-none = "";
inhibited-notification = "<span foreground='red'><sup></sup></span>";
inhibited-none = "";
dnd-inhibited-notification = "<span foreground='red'><sup></sup></span>";
dnd-inhibited-none = "";
};
return-type = "json";
exec-if = "which swaync-client";
exec = "swaync-client -swb";
on-click = "swaync-client -t";
escape = true;
};
"battery" = {
states = {
warning = 30;
critical = 15;
};
format = "{icon} {capacity}%";
format-charging = "󰂄 {capacity}%";
format-plugged = "󱘖 {capacity}%";
format-icons = ["󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹"];
on-click = "";
tooltip = false;
};
}
];
style = concatStrings [
''
* {
font-size: 16px;
font-family: JetBrainsMono Nerd Font, Font Awesome, sans-serif;
font-weight: bold;
}
window#waybar {
/*
background-color: rgba(26,27,38,0);
border-bottom: 1px solid rgba(26,27,38,0);
border-radius: 0px;
color: #${base0F};
*/
background-color: rgba(26,27,38,0);
border-bottom: 1px solid rgba(26,27,38,0);
border-radius: 0px;
color: #${base0F};
}
#workspaces {
/*
Eternal
background: linear-gradient(180deg, #${base00}, #${base01});
margin: 5px 5px 5px 0px;
padding: 0px 10px;
border-radius: 0px 15px 50px 0px;
border: 0px;
font-style: normal;
color: #${base00};
*/
background: linear-gradient(45deg, #${base01}, #${base01});
margin: 5px;
padding: 0px 1px;
border-radius: 15px;
border: 0px;
font-style: normal;
color: #${base00};
}
#workspaces button {
padding: 0px 5px;
margin: 4px 3px;
border-radius: 15px;
border: 0px;
color: #${base00};
background: linear-gradient(45deg, #${base0D}, #${base0E});
opacity: 0.5;
transition: all 0.3s ease-in-out;
}
#workspaces button.active {
padding: 0px 5px;
margin: 4px 3px;
border-radius: 15px;
border: 0px;
color: #${base00};
background: linear-gradient(45deg, #${base0D}, #${base0E});
opacity: 1.0;
min-width: 40px;
transition: all 0.3s ease-in-out;
}
#workspaces button:hover {
border-radius: 15px;
color: #${base00};
background: linear-gradient(45deg, #${base0D}, #${base0E});
opacity: 0.8;
}
tooltip {
background: #${base00};
border: 1px solid #${base0E};
border-radius: 10px;
}
tooltip label {
color: #${base07};
}
#window {
/*
Eternal
color: #${base05};
background: #${base00};
border-radius: 15px;
margin: 5px;
padding: 2px 20px;
*/
margin: 5px;
padding: 2px 20px;
color: #${base05};
background: #${base01};
border-radius: 50px 15px 50px 15px;
}
#memory {
color: #${base0F};
/*
Eternal
background: #${base00};
border-radius: 50px 15px 50px 15px;
margin: 5px;
padding: 2px 20px;
*/
background: #${base01};
margin: 5px;
padding: 2px 20px;
border-radius: 15px 50px 15px 50px;
}
#clock {
color: #${base0B};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#idle_inhibitor {
color: #${base0A};
background: #${base00};
border-radius: 50px 15px 50px 15px;
margin: 5px;
padding: 2px 20px;
}
#cpu {
color: #${base07};
background: #${base00};
border-radius: 50px 15px 50px 15px;
margin: 5px;
padding: 2px 20px;
}
#disk {
color: #${base0F};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#battery {
color: #${base08};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#network {
color: #${base09};
background: #${base00};
border-radius: 50px 15px 50px 15px;
margin: 5px;
padding: 2px 20px;
}
#tray {
color: #${base05};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#pulseaudio {
color: #${base0D};
/*
Eternal
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
*/
background: #${base01};
margin: 4px;
padding: 2px 20px;
border-radius: 50px 15px 50px 15px;
}
#custom-notification {
color: #${base0C};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#custom-startmenu {
color: #${base0E};
background: #${base00};
border-radius: 0px 15px 50px 0px;
margin: 5px 5px 5px 0px;
padding: 2px 20px;
}
#idle_inhibitor {
color: #${base09};
background: #${base00};
border-radius: 15px 50px 15px 50px;
margin: 5px;
padding: 2px 20px;
}
#custom-exit {
color: #${base0E};
background: #${base00};
border-radius: 15px 0px 0px 50px;
margin: 5px 0px 5px 5px;
padding: 2px 20px;
}
''
];
};
}

View File

@@ -0,0 +1,776 @@
{pkgs, ...}: let
# Install any helper scripts shipped in modules/home/waybar/scripts into ~/.config/waybar/scripts
scriptsDir = ./scripts;
scripts = builtins.attrNames (builtins.readDir scriptsDir);
# Inline, improved Cava script packaged via Nix so we don't rely on an external bash file
waybarCava = pkgs.writeShellScriptBin "WaybarCava" ''
set -euo pipefail
# Ensure cava exists
if ! command -v cava >/dev/null 2>&1; then
echo "cava not found in PATH" >&2
exit 1
fi
# Characters for vertical bars (0..7)
bar=""
# Build sed script that:
# - strips semicolons (cava RAW ASCII delimiter)
# - maps digits 0..7 to the corresponding glyph in $bar
dict="s/;//g"
bar_length=''${#bar}
for ((i = 0; i < bar_length; i++)); do
dict+=";s/$i/''${bar:$i:1}/g"
done
# Single-instance guard (kill prior instance cleanly)
RUNTIME_DIR="''${XDG_RUNTIME_DIR:-/tmp}"
pidfile="$RUNTIME_DIR/waybar-cava.pid"
if [ -f "$pidfile" ]; then
oldpid=$(cat "$pidfile" || true)
if [ -n "''${oldpid:-}" ] && kill -0 "$oldpid" 2>/dev/null; then
kill "$oldpid" 2>/dev/null || true
# Give the old pipeline a moment to exit
sleep 0.1 || true
fi
fi
echo $$ > "$pidfile"
# Use a unique temporary config and clean it up on exit
config_file=$(mktemp "''${RUNTIME_DIR}/waybar-cava.XXXXXX.conf")
cleanup() {
rm -f "$config_file" "$pidfile"
}
trap cleanup EXIT INT TERM
cat >"$config_file" <<EOF
[general]
framerate = 30
bars = 10
[input]
method = pulse
source = auto
[output]
method = raw
raw_target = /dev/stdout
data_format = ascii
ascii_max_range = 7
EOF
# Stream cava output and transform
exec cava -p "$config_file" | sed -u "$dict"
'';
# Catppuccin Mocha palette
catppuccinColors = {
rosewater = "#f5e0dc";
flamingo = "#f2cdcd";
pink = "#f5c2e7";
mauve = "#cba6f7";
red = "#f38ba8";
maroon = "#eba0ac";
peach = "#fab387";
yellow = "#f9e2af";
green = "#a6e3a1";
teal = "#94e2d5";
sky = "#89dceb";
sapphire = "#74c7ec";
blue = "#89b4fa";
lavender = "#b4befe";
text = "#cdd6f4";
subtext1 = "#bac2de";
subtext0 = "#a6adc8";
overlay2 = "#9399b2";
overlay1 = "#7f849c";
overlay0 = "#6c7086";
surface2 = "#585b70";
surface1 = "#45475a";
surface0 = "#313244";
base = "#1e1e2e";
mantle = "#181825";
crust = "#11111b";
};
in {
# Ensure bundled Waybar scripts are installed under ~/.config/waybar/scripts
home.file = builtins.listToAttrs (
map (name: {
name = ".config/waybar/scripts/" + name;
value = {
source = "${scriptsDir}/${name}";
executable = true;
};
})
scripts
);
programs.waybar = {
enable = true;
package = pkgs.waybar;
settings = [
{
layer = "top";
exclusive = true;
passthrough = false;
position = "top";
height = 30;
spacing = 4;
"fixed-center" = true;
ipc = true;
"margin-top" = 3;
"margin-left" = 8;
"margin-right" = 8;
modules-left = [
"custom/startmenu"
"hyprland/workspaces"
"custom/cava_mviz"
"battery"
];
modules-center = [
"clock"
"custom/weather"
];
modules-right = [
"idle_inhibitor"
"custom/swaync"
"pulseaudio"
"temperature"
"backlight"
"custom/qs-wallpaper"
"custom/qs-vid-wallpaper"
"custom/power"
];
# ---------- Modules configuration ----------
"hyprland/workspaces" = {
format = "{name}";
persistent-workspaces = {
"*" = 10;
};
};
# Integrated CAVA visualizer using the inline script above
"custom/cava_mviz" = {
exec = "${waybarCava}/bin/WaybarCava";
format = "<span color='#a6e3a1'>[</span> {} <span color='#a6e3a1'>]</span>";
};
idle_inhibitor = {
format = "{icon}";
tooltip = true;
tooltip-format-activated = "Idle inhibitor is active";
tooltip-format-deactivated = "Idle inhibitor is inactive";
format-icons = {
activated = "";
deactivated = "";
};
};
"custom/qs-wallpaper" = {
format = " ";
on-click = "qs-wallpapers-apply";
tooltip = true;
tooltip-format = "Set wallpaper";
};
"custom/qs-vid-wallpaper" = {
format = " ";
on-click = "qs-vid-wallpapers-apply";
tooltip = true;
tooltip-format = "Set video wallpaper";
};
"custom/weather" = {
return-type = "json";
exec = "sh -lc 'WEATHER_ICON_STYLE=emoji WEATHER_TOOLTIP_MARKUP=1 ~/.local/bin/weather'";
interval = 600;
tooltip = true;
};
clock = {
tooltip-format = "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>";
format-alt = "{:%Y-%m-%d}";
};
memory = {
format = "{}% ";
};
temperature = {
critical-threshold = 80;
hwmon-path = "/sys/class/hwmon/hwmon3/temp1_input";
format = "{}°C {icon}";
format-icons = [
""
""
""
];
};
backlight = {
format = "{percent}% {icon}";
format-icons = [
""
""
""
""
""
""
""
""
""
];
};
battery = {
states = {
warning = 30;
critical = 15;
};
format = "{capacity}% {icon}";
format-full = "{capacity}% {icon}";
format-charging = "{capacity}% ";
format-plugged = "{capacity}% ";
format-alt = "{time} {icon}";
interval = 3;
format-icons = [
" "
" "
" "
" "
" "
];
};
"power-profiles-daemon" = {
format = "{icon}";
tooltip-format = "Power profile: {profile}\nDriver: {driver}";
tooltip = true;
format-icons = {
default = "";
performance = "";
balanced = "";
power-saver = "";
};
};
network = {
format-wifi = "Connected ";
on-click = "kitty --class floatwlmenu -e bash ~/.config/scripts/wireless-menu.sh";
format-ethernet = "Connected 󰈀 ";
tooltip-format = "{ifname} via {gwaddr} ";
format-linked = "{ifname} (No IP) ";
format-disconnected = "No Internet ";
};
pulseaudio = {
format = "{volume}% {icon}";
format-bluetooth = "{volume}% {icon} ";
format-bluetooth-muted = " {icon} ";
format-muted = " ";
format-source = " {volume}% ";
format-source-muted = "";
format-icons = {
headphone = "";
hands-free = "";
headset = "";
phone = "";
portable = "";
car = "";
default = [
""
""
" "
];
};
on-click = "pavucontrol";
};
"custom/swaync" = {
tooltip = true;
tooltip-format = "Left Click: Launch Notification Center\nRight Click: Do not Disturb";
format = "{} {icon} ";
format-icons = {
notification = "<span foreground='red'><sup></sup></span>";
none = "";
dnd-notification = "<span foreground='red'><sup></sup></span>";
dnd-none = "";
inhibited-notification = "<span foreground='red'><sup></sup></span>";
inhibited-none = "";
dnd-inhibited-notification = "<span foreground='red'><sup></sup></span>";
dnd-inhibited-none = "";
};
return-type = "json";
exec-if = "which swaync-client";
exec = "swaync-client -swb";
on-click = "systemctl --user start swaync.service; swaync-client -t";
on-click-right = "systemctl --user start swaync.service; swaync-client -d";
escape = true;
};
"custom/power" = {
format = " ";
tooltip = false;
on-click = "qs-wlogout";
};
}
];
# Consolidated style (Catppuccin Mocha) with solid background
style = let
c = catppuccinColors;
in ''
@define-color rosewater ${c.rosewater};
@define-color flamingo ${c.flamingo};
@define-color pink ${c.pink};
@define-color mauve ${c.mauve};
@define-color red ${c.red};
@define-color maroon ${c.maroon};
@define-color peach ${c.peach};
@define-color yellow ${c.yellow};
@define-color green ${c.green};
@define-color teal ${c.teal};
@define-color sky ${c.sky};
@define-color sapphire ${c.sapphire};
@define-color blue ${c.blue};
@define-color lavender ${c.lavender};
@define-color text ${c.text};
@define-color subtext1 ${c.subtext1};
@define-color subtext0 ${c.subtext0};
@define-color overlay2 ${c.overlay2};
@define-color overlay1 ${c.overlay1};
@define-color overlay0 ${c.overlay0};
@define-color surface2 ${c.surface2};
@define-color surface1 ${c.surface1};
@define-color surface0 ${c.surface0};
@define-color base ${c.base};
@define-color mantle ${c.mantle};
@define-color crust ${c.crust};
* {
font-family: "Hack Nerd Font", "Font Awesome 6 Free", "Font Awesome 6 Brands", monospace;
font-weight: 400;
font-size: 18px;
border-radius: 7px;
}
window#waybar {
background-color: @base;
color: #ffffff;
border-radius: 0px;
transition-property: background-color;
transition-duration: 0.5s;
}
/* Override white color for idle_inhibitor specifically */
window#waybar > box > #idle_inhibitor {
color: #f38ba8; /* red by default */
}
window#waybar > box > #idle_inhibitor.activated {
color: #a6e3a1; /* green when activated */
}
window#waybar > box > #idle_inhibitor.deactivated {
color: #f38ba8; /* red when deactivated */
}
tooltip {
background: @base;
border-radius: 7px;
}
button {
box-shadow: inset 0 3px transparent;
border: none;
border-radius: 0;
}
button:hover {
background: inherit;
}
#pulseaudio:hover {
background-color: @mantle;
}
#workspaces button {
padding: 0 5px;
background-color: transparent;
}
#workspaces button:hover {
background: rgba(0, 0, 0, 0.3);
border-radius: 7px;
}
/* Hyprland workspaces - based on working waybar-jak-catppuccin */
#workspaces button {
color: @sapphire; /* default/inactive workspaces - pale blue */
background-color: transparent;
padding-top: 4px;
padding-bottom: 4px;
padding-right: 6px;
padding-left: 4px;
}
#workspaces button.active {
color: @green; /* active workspace - green */
background: transparent;
border-radius: 15px;
}
#workspaces button.focused {
color: @rosewater; /* focused workspace - rosewater */
background: transparent;
border-radius: 15px;
}
#workspaces button.urgent {
color: @crust;
background: transparent;
border-radius: 15px;
}
#workspaces button:hover {
background: transparent;
color: @flamingo;
border-radius: 15px;
}
#workspaces button.empty {
color: @red; /* empty workspace - red */
}
#workspaces button.urgent {
background-color: #eb4d4b;
}
#mode {
background-color: #64727d;
box-shadow: inset 0 0px #ffffff;
background: rgba(5, 5, 5, 0.3);
color: #000000;
padding: 1px 10px 1px 10px;
border-radius: 0px 0px 0px 0px;
margin-top: 5px;
}
#clock,
#battery,
#cpu,
#memory,
#disk,
#temperature,
#backlight,
#network,
#custom-spotify,
#pulseaudio,
#wireplumber,
#custom-media,
#custom-waypaper,
#tray,
#mode,
#custom-weather,
#idle_inhibitor,
#scratchpad,
#power-profiles-daemon,
#custom-cava,
#custom-cava_mviz,
#custom-gpt,
#custom-mspowers,
#custom-qs-wallpaper,
#custom-qs-vid-wallpaper,
#custom-swaync,
#mpd {
background: @base;
padding: 1px 10px 1px 10px;
margin-top: 5px;
}
#window,
#workspaces {
background: @base;
color: @rosewater;
padding: 1px 1px 1px 1px;
margin-top: 5px;
}
.modules-left > widget:first-child > #workspaces {
margin-left: 5px;
}
.modules-right > widget:last-child > #workspaces {
margin-right: 0;
}
#clock {
color: @pink;
margin-top: 5px;
margin-right: 5px;
}
#custom-mspowers {
color: @yellow;
margin-top: 5px;
}
#clock:hover {
background-color: @mantle;
}
#custom-weather {
color: @sky;
padding: 1px 6px 1px 8px;
}
#custom-spotify {
color: @maroon;
padding: 1px 6px 1px 8px;
}
#custom-waypaper:hover {
background-color: @mantle;
}
#custom-cava,
#custom-cava_mviz {
background: @base;
color: @mauve;
padding: 1px 10px;
margin-left: 8px;
}
#custom-cava:hover,
#custom-cava_mviz:hover {
background-color: @mantle;
}
#custom-gpt {
font-size: 20px;
background-position: center;
background-repeat: no-repeat;
background-size: 15px 15px;
color: @mauve;
border-radius: 7px;
}
#custom-gpt:hover {
background-color: @mantle;
}
#custom-weather:hover {
background-color: @mantle;
}
#cava-player .status {
font-size: 18px;
color: #ffffff;
}
#custom-power {
color: @mauve;
background-color: @base;
margin-right: 5px;
margin-top: 5px;
padding: 1px 5px 1px 2px;
}
#custom-power:hover {
background-color: @mantle;
}
#custom-menu {
color: @blue;
background-color: @base;
margin-left: 5px;
margin-top: 5px;
padding: 1px 10px 1px 10px;
font-size: 18px;
min-width: 30px;
}
#custom-menu:hover {
background-color: @mantle;
}
#custom-qs-wallpaper,
#custom-qs-vid-wallpaper {
color: @sapphire;
background-color: @base;
font-size: 18px;
min-width: 20px;
}
#custom-qs-wallpaper:hover,
#custom-qs-vid-wallpaper:hover {
background-color: @mantle;
}
#custom-swaync {
color: @green;
background-color: @base;
}
#custom-swaync.notification,
#custom-swaync.dnd-notification,
#custom-swaync.inhibited-notification,
#custom-swaync.dnd-inhibited-notification {
color: @red;
}
#custom-swaync:hover {
background-color: @mantle;
}
#battery {
color: @mauve;
}
#battery.charging,
#battery.plugged {
color: @green;
}
@keyframes blink {
to {
background: @base;
color: #000000;
padding: 1px 10px 1px 10px;
margin-top: 5px;
}
}
#battery.critical:not(.charging) {
color: #ffffff;
animation-name: blink;
animation-duration: 0.5s;
animation-timing-function: steps(12);
animation-iteration-count: infinite;
animation-direction: alternate;
}
#battery:hover {
background-color: @mantle;
}
#power-profiles-daemon {
padding-right: 15px;
color: #000000;
}
#power-profiles-daemon.performance {
color: #ffffff;
}
#power-profiles-daemon.balanced {
color: #ffffff;
}
#power-profiles-daemon.power-saver {
color: #000000;
}
label:focus {
background: @base;
}
#memory {
color: @yellow;
}
#memory:hover {
background-color: @mantle;
}
#disk {
background: @base;
}
#backlight {
color: @green;
}
#backlight:hover {
background-color: @mantle;
}
#network {
color: @peach;
}
#network.disconnected {
color: @red;
}
#network:hover {
background-color: @mantle;
}
#pulseaudio {
color: @peach;
}
#pulseaudio.muted {
color: @peach;
}
#temperature {
background: @base;
color: @yellow;
padding: 1px 10px 1px 10px;
margin-top: 5px;
}
#temperature.critical {
background-color: #eb4d4b;
}
/* Idle inhibitor - comprehensive targeting without !important */
#idle_inhibitor,
.idle_inhibitor,
button#idle_inhibitor,
.module#idle_inhibitor,
window#waybar #idle_inhibitor,
window#waybar .idle_inhibitor,
window#waybar button#idle_inhibitor {
color: #f38ba8; /* red when deactivated */
background-color: #1e1e2e;
}
#idle_inhibitor.activated,
.idle_inhibitor.activated,
button#idle_inhibitor.activated,
.module#idle_inhibitor.activated,
window#waybar #idle_inhibitor.activated,
window#waybar .idle_inhibitor.activated,
window#waybar button#idle_inhibitor.activated {
color: #a6e3a1; /* green when activated */
background-color: #1e1e2e;
}
#idle_inhibitor.deactivated,
.idle_inhibitor.deactivated,
button#idle_inhibitor.deactivated,
.module#idle_inhibitor.deactivated,
window#waybar #idle_inhibitor.deactivated,
window#waybar .idle_inhibitor.deactivated,
window#waybar button#idle_inhibitor.deactivated {
color: #f38ba8; /* red when deactivated */
background-color: #1e1e2e;
}
#idle_inhibitor:hover {
background-color: @mantle;
}
'';
};
}

View File

@@ -0,0 +1,711 @@
{pkgs, ...}: let
# Install any helper scripts shipped in modules/home/waybar/scripts into ~/.config/waybar/scripts
scriptsDir = ./scripts;
scripts = builtins.attrNames (builtins.readDir scriptsDir);
# Inline, improved Cava script packaged via Nix so we don't rely on an external bash file
waybarCava = pkgs.writeShellScriptBin "WaybarCava" ''
set -euo pipefail
# Ensure cava exists
if ! command -v cava >/dev/null 2>&1; then
echo "cava not found in PATH" >&2
exit 1
fi
# Characters for vertical bars (0..7)
bar=""
# Build sed script that:
# - strips semicolons (cava RAW ASCII delimiter)
# - maps digits 0..7 to the corresponding glyph in $bar
dict="s/;//g"
bar_length=''${#bar}
for ((i = 0; i < bar_length; i++)); do
dict+=";s/$i/''${bar:$i:1}/g"
done
# Single-instance guard (kill prior instance cleanly)
RUNTIME_DIR="''${XDG_RUNTIME_DIR:-/tmp}"
pidfile="$RUNTIME_DIR/waybar-cava.pid"
if [ -f "$pidfile" ]; then
oldpid=$(cat "$pidfile" || true)
if [ -n "''${oldpid:-}" ] && kill -0 "$oldpid" 2>/dev/null; then
kill "$oldpid" 2>/dev/null || true
# Give the old pipeline a moment to exit
sleep 0.1 || true
fi
fi
echo $$ > "$pidfile"
# Use a unique temporary config and clean it up on exit
config_file=$(mktemp "''${RUNTIME_DIR}/waybar-cava.XXXXXX.conf")
cleanup() {
rm -f "$config_file" "$pidfile"
}
trap cleanup EXIT INT TERM
cat >"$config_file" <<EOF
[general]
framerate = 30
bars = 10
[input]
method = pulse
source = auto
[output]
method = raw
raw_target = /dev/stdout
data_format = ascii
ascii_max_range = 7
EOF
# Stream cava output and transform
exec cava -p "$config_file" | sed -u "$dict"
'';
in {
# Ensure bundled Waybar scripts are installed under ~/.config/waybar/scripts
home.file = builtins.listToAttrs (
map (name: {
name = ".config/waybar/scripts/" + name;
value = {
source = "${scriptsDir}/${name}";
executable = true;
};
})
scripts
);
programs.waybar = {
enable = true;
package = pkgs.waybar;
settings = [
{
layer = "top";
exclusive = true;
passthrough = false;
position = "top";
height = 30;
spacing = 4;
"fixed-center" = true;
ipc = true;
"margin-top" = 3;
"margin-left" = 8;
"margin-right" = 8;
modules-left = [
"custom/startmenu"
"hyprland/workspaces"
"custom/cava_mviz"
"battery"
];
modules-center = [
"clock"
"custom/weather"
];
modules-right = [
"custom/swaync"
"idle_inhibitor"
"pulseaudio"
"temperature"
"backlight"
"custom/qs-wallpaper"
"custom/qs-vid-wallpaper"
"custom/power"
];
# ---------- Modules configuration ----------
"hyprland/workspaces" = {
format = "{name}";
persistent-workspaces = {
"*" = 10;
};
};
"custom/menu" = {
format = " ";
# on-click = "pkill rofi || rofi -show drun -modi run,drun,filebrowser,window";
on-click = "launch-nwg-menu";
on-click-middle = "$HOME/.config/hypr/UserScripts/WallpaperSelect.sh";
on-click-right = "$HOME/.config/hypr/scripts/WaybarLayout.sh";
tooltip = true;
tooltip-format = "Left Click: Rofi Menu\nMiddle Click: Wallpaper Menu\nRight Click: Waybar Layout Menu";
};
# Integrated CAVA visualizer using the inline script above
"custom/cava_mviz" = {
exec = "${waybarCava}/bin/WaybarCava";
format = "<span color='#a6e3a1'>[</span> {} <span color='#a6e3a1'>]</span>";
};
idle_inhibitor = {
format = "{icon}";
tooltip = true;
tooltip-format-activated = "Idle inhibitor is active";
tooltip-format-deactivated = "Idle inhibitor is inactive";
format-icons = {
activated = "";
deactivated = "";
};
};
"custom/qs-wallpaper" = {
format = " ";
on-click = "qs-wallpapers-apply";
tooltip = true;
tooltip-format = "Set wallpaper";
};
"custom/qs-vid-wallpaper" = {
format = " ";
on-click = "qs-vid-wallpapers-apply";
tooltip = true;
tooltip-format = "Set video wallpaper";
};
"custom/weather" = {
return-type = "json";
exec = "sh -lc 'WEATHER_ICON_STYLE=emoji WEATHER_TOOLTIP_MARKUP=1 ~/.local/bin/weather'";
interval = 600;
tooltip = true;
};
clock = {
tooltip-format = "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>";
format-alt = "{:%Y-%m-%d}";
};
memory = {
format = "{}% ";
};
temperature = {
critical-threshold = 80;
hwmon-path = "/sys/class/hwmon/hwmon3/temp1_input";
format = "{}°C {icon}";
format-icons = [
""
""
""
];
};
backlight = {
format = "{percent}% {icon}";
format-icons = [
""
""
""
""
""
""
""
""
""
];
};
battery = {
states = {
warning = 30;
critical = 15;
};
format = "{capacity}% {icon}";
format-full = "{capacity}% {icon}";
format-charging = "{capacity}% ";
format-plugged = "{capacity}% ";
format-alt = "{time} {icon}";
interval = 3;
format-icons = [
" "
" "
" "
" "
" "
];
};
"power-profiles-daemon" = {
format = "{icon}";
tooltip-format = "Power profile: {profile}\nDriver: {driver}";
tooltip = true;
format-icons = {
default = "";
performance = "";
balanced = "";
power-saver = "";
};
};
network = {
format-wifi = "Connected ";
on-click = "kitty --class floatwlmenu -e bash ~/.config/scripts/wireless-menu.sh";
format-ethernet = "Connected 󰈀 ";
tooltip-format = "{ifname} via {gwaddr} ";
format-linked = "{ifname} (No IP) ";
format-disconnected = "No Internet ";
};
pulseaudio = {
format = "{volume}% {icon}";
format-bluetooth = "{volume}% {icon} ";
format-bluetooth-muted = " {icon} ";
format-muted = " ";
format-source = " {volume}% ";
format-source-muted = "";
format-icons = {
headphone = "";
hands-free = "";
headset = "";
phone = "";
portable = "";
car = "";
default = [
""
""
" "
];
};
on-click = "pavucontrol";
};
"custom/swaync" = {
tooltip = true;
tooltip-format = "Left Click: Launch Notification Center\nRight Click: Do not Disturb";
format = "{} {icon} ";
format-icons = {
notification = "<span foreground='red'><sup></sup></span>";
none = "";
dnd-notification = "<span foreground='red'><sup></sup></span>";
dnd-none = "";
inhibited-notification = "<span foreground='red'><sup></sup></span>";
inhibited-none = "";
dnd-inhibited-notification = "<span foreground='red'><sup></sup></span>";
dnd-inhibited-none = "";
};
return-type = "json";
exec-if = "which swaync-client";
exec = "swaync-client -swb";
on-click = "systemctl --user start swaync.service; swaync-client -t";
on-click-right = "systemctl --user start swaync.service; swaync-client -d";
escape = true;
};
"custom/power" = {
format = " ";
tooltip = false;
on-click = "qs-wlogout";
};
}
];
# Transparent style based on transparent.css
style = ''
* {
font-family: "Hack Nerd Font";
font-weight: 400;
font-size: 18px;
border-radius: 7px;
}
window#waybar {
background-color: transparent;
color: #ffffff;
border-radius: 8px;
}
#waybar > box {
margin: 4px 8px 0 8px;
padding: 2px;
background-color: rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
tooltip {
background: rgb(30, 30, 46);
border-radius: 7px;
}
button {
box-shadow: inset 0 3px transparent;
border: none;
border-radius: 0;
}
button:hover {
background: inherit;
}
#workspaces button {
padding: 0 5px;
background-color: transparent;
}
#workspaces button:hover {
background: rgba(255, 255, 255, 0.1);
border-radius: 7px;
}
/* Hyprland workspaces - based on working waybar-jak-catppuccin */
#workspaces button {
color: #74c7ec; /* default/inactive workspaces - sapphire */
background-color: transparent;
padding-top: 4px;
padding-bottom: 4px;
padding-right: 6px;
padding-left: 4px;
}
#workspaces button.active {
color: #a6e3a1; /* active workspace - green */
background: transparent;
border-radius: 15px;
}
#workspaces button.focused {
color: #f5e0dc; /* focused workspace - rosewater */
background: transparent;
border-radius: 15px;
}
#workspaces button.urgent {
color: #11111b; /* crust */
background: transparent;
border-radius: 15px;
}
#workspaces button:hover {
background: transparent;
color: #f2cdcd; /* flamingo */
border-radius: 15px;
}
#workspaces button.empty {
color: #f38ba8; /* empty workspace - red */
}
#workspaces button.urgent {
background-color: #eb4d4b;
}
#mode {
background-color: #64727d;
box-shadow: inset 0 0px #ffffff;
background: rgba(5, 5, 5, 0.3);
color: #000000;
padding: 1px 10px 1px 10px;
border-radius: 0px 0px 0px 0px;
margin-top: 5px;
}
#clock,
#battery,
#cpu,
#memory,
#disk,
#temperature,
#backlight,
#network,
#custom-spotify,
#pulseaudio,
#wireplumber,
#custom-media,
#custom-waypaper,
#tray,
#mode,
#custom-weather,
#idle_inhibitor,
#scratchpad,
#custom-power #power-profiles-daemon,
#custom-cava,
#custom-cava_mviz,
#custom-gpt,
#custom-menu,
#custom-qs-wallpaper,
#custom-qs-vid-wallpaper,
#custom-swaync,
#mpd {
background: rgba(30, 30, 46, 0);
padding: 1px 10px 1px 10px;
}
#window,
#workspaces {
color: #f5e0dc;
padding: 1px 1px 1px 1px;
}
.modules-right > widget:last-child > #workspaces {
margin-right: 0;
}
#clock {
color: #ffffff;
margin-right: 5px;
}
#clock:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-weather {
color: #ffffff;
padding: 1px 6px 1px 8px;
}
#custom-weather:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-spotify {
color: #ffffff;
padding: 1px 6px 1px 8px;
}
#custom-spotify:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-cava,
#custom-cava_mviz {
background: rgb(30, 30, 46);
color: #ffffff;
padding: 1px 10px;
margin-left: 8px;
}
#custom-cava:hover,
#custom-cava_mviz:hover {
background-color: #181825;
}
#custom-gpt {
font-size: 20px;
background-position: center;
background-repeat: no-repeat;
background-size: 15px 15px;
color: #ffffff;
border-radius: 7px;
}
#custom-gpt:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#cava-player .status {
font-size: 18px;
color: #ffffff;
}
#custom-power {
color: #ffffff;
padding: 1px 5px 1px 3px;
margin-right: 1px;
}
#custom-power:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-menu {
color: #89b4fa;
padding: 1px 10px 1px 10px;
margin-left: 5px;
}
#custom-menu:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-qs-wallpaper,
#custom-qs-vid-wallpaper {
color: #74c7ec;
}
#custom-qs-wallpaper:hover,
#custom-qs-vid-wallpaper:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-swaync {
color: #a6e3a1;
}
#custom-swaync.notification,
#custom-swaync.dnd-notification,
#custom-swaync.inhibited-notification,
#custom-swaync.dnd-inhibited-notification {
color: #f38ba8;
}
#custom-swaync:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#battery {
color: #ffffff;
}
#battery.charging,
#battery.plugged {
color: #85eb81;
}
#battery:hover {
background-color: rgba(255, 255, 255, 0.1);
}
@keyframes blink {
to {
background: rgb(30, 30, 46);
color: #000000;
padding: 1px 10px 1px 10px;
margin-top: 5px;
}
}
#battery.critical:not(.charging) {
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;
color: #000000;
}
#power-profiles-daemon.performance {
color: #ffffff;
}
#power-profiles-daemon.balanced {
color: #ffffff;
}
#power-profiles-daemon.power-saver {
color: #000000;
}
label:focus {
background: rgb(30, 30, 46);
}
#memory {
color: #ffffff;
padding: 1px 9px 1px 5px;
}
#memory:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#disk {
background: rgb(30, 30, 46);
}
#backlight {
color: #ffffff;
}
#network {
color: #ffffff;
}
#network.disconnected {
color: #f2564b;
}
#network:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#pulseaudio {
color: #ffffff;
}
#pulseaudio.muted {
color: #ffffff;
}
#wireplumber {
color: #000000;
}
#pulseaudio:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#wireplumber.muted {
background: rgb(30, 30, 46);
padding: 1px 10px 1px 10px;
margin-top: 5px;
}
#custom-media {
color: #2a5c45;
min-width: 100px;
}
#custom-media.custom-spotify {
background: rgb(30, 30, 46);
padding: 1px 10px 1px 10px;
margin-top: 5px;
}
#custom-media.custom-vlc {
background: rgb(30, 30, 46);
margin-top: 5px;
}
#temperature {
color: #ffffff;
}
#temperature.critical {
background-color: #eb4d4b;
}
#temperature:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#tray {
background-color: #2980b9;
}
#tray > .passive {
-gtk-icon-effect: dim;
}
#tray > .needs-attention {
-gtk-icon-effect: highlight;
background-color: #eb4d4b;
}
/* Idle inhibitor - coffee mug color changes */
#idle_inhibitor {
color: #f38ba8; /* default red (deactivated) */
}
#idle_inhibitor.activated {
color: #a6e3a1; /* green when activated */
}
#idle_inhibitor.deactivated {
color: #f38ba8; /* red when deactivated */
}
#idle_inhibitor:hover {
background-color: rgba(255, 255, 255, 0.1);
}
'';
};
}

View File

@@ -0,0 +1,711 @@
{pkgs, ...}: let
# Install any helper scripts shipped in modules/home/waybar/scripts into ~/.config/waybar/scripts
scriptsDir = ./scripts;
scripts = builtins.attrNames (builtins.readDir scriptsDir);
# Inline, improved Cava script packaged via Nix so we don't rely on an external bash file
waybarCava = pkgs.writeShellScriptBin "WaybarCava" ''
set -euo pipefail
# Ensure cava exists
if ! command -v cava >/dev/null 2>&1; then
echo "cava not found in PATH" >&2
exit 1
fi
# Characters for vertical bars (0..7)
bar=""
# Build sed script that:
# - strips semicolons (cava RAW ASCII delimiter)
# - maps digits 0..7 to the corresponding glyph in $bar
dict="s/;//g"
bar_length=''${#bar}
for ((i = 0; i < bar_length; i++)); do
dict+=";s/$i/''${bar:$i:1}/g"
done
# Single-instance guard (kill prior instance cleanly)
RUNTIME_DIR="''${XDG_RUNTIME_DIR:-/tmp}"
pidfile="$RUNTIME_DIR/waybar-cava.pid"
if [ -f "$pidfile" ]; then
oldpid=$(cat "$pidfile" || true)
if [ -n "''${oldpid:-}" ] && kill -0 "$oldpid" 2>/dev/null; then
kill "$oldpid" 2>/dev/null || true
# Give the old pipeline a moment to exit
sleep 0.1 || true
fi
fi
echo $$ > "$pidfile"
# Use a unique temporary config and clean it up on exit
config_file=$(mktemp "''${RUNTIME_DIR}/waybar-cava.XXXXXX.conf")
cleanup() {
rm -f "$config_file" "$pidfile"
}
trap cleanup EXIT INT TERM
cat >"$config_file" <<EOF
[general]
framerate = 30
bars = 10
[input]
method = pulse
source = auto
[output]
method = raw
raw_target = /dev/stdout
data_format = ascii
ascii_max_range = 7
EOF
# Stream cava output and transform
exec cava -p "$config_file" | sed -u "$dict"
'';
in {
# Ensure bundled Waybar scripts are installed under ~/.config/waybar/scripts
home.file = builtins.listToAttrs (
map (name: {
name = ".config/waybar/scripts/" + name;
value = {
source = "${scriptsDir}/${name}";
executable = true;
};
})
scripts
);
programs.waybar = {
enable = true;
package = pkgs.waybar;
settings = [
{
layer = "top";
exclusive = true;
passthrough = false;
position = "top";
height = 30;
spacing = 4;
"fixed-center" = true;
ipc = true;
"margin-top" = 3;
"margin-left" = 8;
"margin-right" = 8;
modules-left = [
"custom/startmenu"
"hyprland/workspaces"
"custom/cava_mviz"
"battery"
];
modules-center = [
"clock"
"custom/weather"
];
modules-right = [
"idle_inhibitor"
"custom/swaync"
"pulseaudio"
"temperature"
"backlight"
"custom/qs-wallpaper"
"custom/qs-vid-wallpaper"
"custom/power"
];
# ---------- Modules configuration ----------
"hyprland/workspaces" = {
format = "{name}";
persistent-workspaces = {
"*" = 10;
};
};
"custom/menu" = {
format = " ";
# on-click = "pkill rofi || rofi -show drun -modi run,drun,filebrowser,window";
on-click = "launch-nwg-menu";
on-click-middle = "$HOME/.config/hypr/UserScripts/WallpaperSelect.sh";
on-click-right = "$HOME/.config/hypr/scripts/WaybarLayout.sh";
tooltip = true;
tooltip-format = "Left Click: Rofi Menu\nMiddle Click: Wallpaper Menu\nRight Click: Waybar Layout Menu";
};
# Integrated CAVA visualizer using the inline script above
"custom/cava_mviz" = {
exec = "${waybarCava}/bin/WaybarCava";
format = "<span color='#a6e3a1'>[</span> {} <span color='#a6e3a1'>]</span>";
};
idle_inhibitor = {
format = "{icon}";
tooltip = true;
tooltip-format-activated = "Idle inhibitor is active";
tooltip-format-deactivated = "Idle inhibitor is inactive";
format-icons = {
activated = "";
deactivated = "";
};
};
"custom/qs-wallpaper" = {
format = " ";
on-click = "qs-wallpapers-apply";
tooltip = true;
tooltip-format = "Set wallpaper";
};
"custom/qs-vid-wallpaper" = {
format = " ";
on-click = "qs-vid-wallpapers-apply";
tooltip = true;
tooltip-format = "Set video wallpaper";
};
"custom/weather" = {
return-type = "json";
exec = "sh -lc 'WEATHER_ICON_STYLE=emoji WEATHER_TOOLTIP_MARKUP=1 ~/.local/bin/weather'";
interval = 600;
tooltip = true;
};
clock = {
tooltip-format = "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>";
format-alt = "{:%Y-%m-%d}";
};
memory = {
format = "{}% ";
};
temperature = {
critical-threshold = 80;
hwmon-path = "/sys/class/hwmon/hwmon3/temp1_input";
format = "{}°C {icon}";
format-icons = [
""
""
""
];
};
backlight = {
format = "{percent}% {icon}";
format-icons = [
""
""
""
""
""
""
""
""
""
];
};
battery = {
states = {
warning = 30;
critical = 15;
};
format = "{capacity}% {icon}";
format-full = "{capacity}% {icon}";
format-charging = "{capacity}% ";
format-plugged = "{capacity}% ";
format-alt = "{time} {icon}";
interval = 3;
format-icons = [
" "
" "
" "
" "
" "
];
};
"power-profiles-daemon" = {
format = "{icon}";
tooltip-format = "Power profile: {profile}\nDriver: {driver}";
tooltip = true;
format-icons = {
default = "";
performance = "";
balanced = "";
power-saver = "";
};
};
network = {
format-wifi = "Connected ";
on-click = "kitty --class floatwlmenu -e bash ~/.config/scripts/wireless-menu.sh";
format-ethernet = "Connected 󰈀 ";
tooltip-format = "{ifname} via {gwaddr} ";
format-linked = "{ifname} (No IP) ";
format-disconnected = "No Internet ";
};
pulseaudio = {
format = "{volume}% {icon}";
format-bluetooth = "{volume}% {icon} ";
format-bluetooth-muted = " {icon} ";
format-muted = " ";
format-source = " {volume}% ";
format-source-muted = "";
format-icons = {
headphone = "";
hands-free = "";
headset = "";
phone = "";
portable = "";
car = "";
default = [
""
""
" "
];
};
on-click = "pavucontrol";
};
"custom/swaync" = {
tooltip = true;
tooltip-format = "Left Click: Launch Notification Center\nRight Click: Do not Disturb";
format = "{} {icon} ";
format-icons = {
notification = "<span foreground='red'><sup></sup></span>";
none = "";
dnd-notification = "<span foreground='red'><sup></sup></span>";
dnd-none = "";
inhibited-notification = "<span foreground='red'><sup></sup></span>";
inhibited-none = "";
dnd-inhibited-notification = "<span foreground='red'><sup></sup></span>";
dnd-inhibited-none = "";
};
return-type = "json";
exec-if = "which swaync-client";
exec = "swaync-client -swb";
on-click = "systemctl --user start swaync.service; swaync-client -t";
on-click-right = "systemctl --user start swaync.service; swaync-client -d";
escape = true;
};
"custom/power" = {
format = " ";
tooltip = false;
on-click = "qs-wlogout";
};
}
];
# Ultradark style based on ultradark.css
style = ''
* {
font-family: "Hack Nerd Font";
font-weight: 400;
font-size: 18px;
border-radius: 7px;
}
window#waybar {
background-color: transparent;
color: #e5e5e5;
border-radius: 8px;
}
#waybar > box {
margin: 4px 8px 0 8px;
padding: 2px;
background-color: rgba(0, 0, 0, 1);
border-radius: 8px;
}
tooltip {
background: rgb(30, 30, 46);
border-radius: 7px;
}
button {
box-shadow: inset 0 3px transparent;
border: none;
border-radius: 0;
}
button:hover {
background: inherit;
}
#workspaces button {
padding: 0 5px;
background-color: transparent;
}
#workspaces button:hover {
background: rgba(255, 255, 255, 0.1);
border-radius: 7px;
}
/* Hyprland workspaces - based on working waybar-jak-catppuccin */
#workspaces button {
color: #74c7ec; /* default/inactive workspaces - sapphire */
background-color: transparent;
padding-top: 4px;
padding-bottom: 4px;
padding-right: 6px;
padding-left: 4px;
}
#workspaces button.active {
color: #a6e3a1; /* active workspace - green */
background: transparent;
border-radius: 15px;
}
#workspaces button.focused {
color: #f5e0dc; /* focused workspace - rosewater */
background: transparent;
border-radius: 15px;
}
#workspaces button.urgent {
color: #11111b; /* crust */
background: transparent;
border-radius: 15px;
}
#workspaces button:hover {
background: transparent;
color: #f2cdcd; /* flamingo */
border-radius: 15px;
}
#workspaces button.empty {
color: #f38ba8; /* empty workspace - red */
}
#workspaces button.urgent {
background-color: #eb4d4b;
}
#mode {
background-color: #64727d;
box-shadow: inset 0 0px #ffffff;
background: rgba(5, 5, 5, 0.3);
color: #000000;
padding: 1px 10px 1px 10px;
border-radius: 0px 0px 0px 0px;
margin-top: 5px;
}
#clock,
#battery,
#cpu,
#memory,
#disk,
#temperature,
#backlight,
#network,
#custom-spotify,
#pulseaudio,
#wireplumber,
#custom-media,
#custom-waypaper,
#tray,
#mode,
#custom-weather,
#idle_inhibitor,
#scratchpad,
#custom-power #power-profiles-daemon,
#custom-cava,
#custom-cava_mviz,
#custom-gpt,
#custom-menu,
#custom-qs-wallpaper,
#custom-qs-vid-wallpaper,
#custom-swaync,
#mpd {
background: rgba(30, 30, 46, 0);
padding: 1px 10px 1px 10px;
}
#window,
#workspaces {
color: #f5e0dc;
padding: 1px 1px 1px 1px;
}
.modules-right > widget:last-child > #workspaces {
margin-right: 0;
}
#clock {
color: #e5e5e5;
margin-right: 5px;
}
#clock:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-weather {
color: #e5e5e5;
padding: 1px 6px 1px 8px;
}
#custom-weather:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-spotify {
color: #e5e5e5;
padding: 1px 6px 1px 8px;
}
#custom-spotify:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-cava,
#custom-cava_mviz {
background: rgb(30, 30, 46);
color: #e5e5e5;
padding: 1px 10px;
margin-left: 8px;
}
#custom-cava:hover,
#custom-cava_mviz:hover {
background-color: #181825;
}
#custom-gpt {
font-size: 20px;
background-position: center;
background-repeat: no-repeat;
background-size: 15px 15px;
color: #e5e5e5;
border-radius: 7px;
}
#custom-gpt:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#cava-player .status {
font-size: 18px;
color: #e5e5e5;
}
#custom-power {
color: #e5e5e5;
padding: 1px 5px 1px 3px;
margin-right: 1px;
}
#custom-power:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-menu {
color: #89b4fa;
padding: 1px 10px 1px 10px;
margin-left: 5px;
}
#custom-menu:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-qs-wallpaper,
#custom-qs-vid-wallpaper {
color: #74c7ec;
}
#custom-qs-wallpaper:hover,
#custom-qs-vid-wallpaper:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#custom-swaync {
color: #a6e3a1;
}
#custom-swaync.notification,
#custom-swaync.dnd-notification,
#custom-swaync.inhibited-notification,
#custom-swaync.dnd-inhibited-notification {
color: #f38ba8;
}
#custom-swaync:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#battery {
color: #e5e5e5;
}
#battery.charging,
#battery.plugged {
color: #85eb81;
}
#battery:hover {
background-color: rgba(255, 255, 255, 0.1);
}
@keyframes blink {
to {
background: rgb(30, 30, 46);
color: #000000;
padding: 1px 10px 1px 10px;
margin-top: 5px;
}
}
#battery.critical:not(.charging) {
color: #e5e5e5;
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;
color: #000000;
}
#power-profiles-daemon.performance {
color: #e5e5e5;
}
#power-profiles-daemon.balanced {
color: #e5e5e5;
}
#power-profiles-daemon.power-saver {
color: #000000;
}
label:focus {
background: rgb(30, 30, 46);
}
#memory {
color: #e5e5e5;
padding: 1px 9px 1px 5px;
}
#memory:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#disk {
background: rgb(30, 30, 46);
}
#backlight {
color: #e5e5e5;
}
#network {
color: #e5e5e5;
}
#network.disconnected {
color: #f2564b;
}
#network:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#pulseaudio {
color: #e5e5e5;
}
#pulseaudio.muted {
color: #e5e5e5;
}
#wireplumber {
color: #000000;
}
#pulseaudio:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#wireplumber.muted {
background: rgb(30, 30, 46);
padding: 1px 10px 1px 10px;
margin-top: 5px;
}
#custom-media {
color: #2a5c45;
min-width: 100px;
}
#custom-media.custom-spotify {
background: rgb(30, 30, 46);
padding: 1px 10px 1px 10px;
margin-top: 5px;
}
#custom-media.custom-vlc {
background: rgb(30, 30, 46);
margin-top: 5px;
}
#temperature {
color: #e5e5e5;
}
#temperature.critical {
background-color: #eb4d4b;
}
#temperature:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#tray {
background-color: #2980b9;
}
#tray > .passive {
-gtk-icon-effect: dim;
}
#tray > .needs-attention {
-gtk-icon-effect: highlight;
background-color: #eb4d4b;
}
/* Idle inhibitor - coffee mug color changes */
#idle_inhibitor {
color: #f38ba8; /* default red (deactivated) */
}
#idle_inhibitor.activated {
color: #a6e3a1; /* green when activated */
}
#idle_inhibitor.deactivated {
color: #f38ba8; /* red when deactivated */
}
#idle_inhibitor:hover {
background-color: rgba(255, 255, 255, 0.1);
}
'';
};
}

View File

@@ -0,0 +1,568 @@
{pkgs, ...}: let
scriptsDir = ../waybar/scripts;
scripts = builtins.attrNames (builtins.readDir scriptsDir);
waybarCava = pkgs.writeShellScriptBin "WaybarCava" ''
set -euo pipefail
if ! command -v cava >/dev/null 2>&1; then
echo "cava not found in PATH" >&2
exit 1
fi
bar=""
dict="s/;//g"
bar_length=''${#bar}
for ((i = 0; i < bar_length; i++)); do
dict+=";s/$i/''${bar:$i:1}/g"
done
RUNTIME_DIR="''${XDG_RUNTIME_DIR:-/tmp}"
pidfile="$RUNTIME_DIR/waybar-cava.pid"
if [ -f "$pidfile" ]; then
oldpid=$(cat "$pidfile" || true)
if [ -n "''${oldpid:-}" ] && kill -0 "$oldpid" 2>/dev/null; then
kill "$oldpid" 2>/dev/null || true
sleep 0.1 || true
fi
fi
echo $$ > "$pidfile"
config_file=$(mktemp "''${RUNTIME_DIR}/waybar-cava.XXXXXX.conf")
cleanup() { rm -f "$config_file" "$pidfile"; }
trap cleanup EXIT INT TERM
cat >"$config_file" <<EOF
[general]
framerate = 30
bars = 10
[input]
method = pulse
source = auto
[output]
method = raw
raw_target = /dev/stdout
data_format = ascii
ascii_max_range = 7
EOF
exec cava -p "$config_file" | sed -u "$dict"
'';
catppuccinColors = {
rosewater = "#f5e0dc";
flamingo = "#f2cdcd";
pink = "#f5c2e7";
mauve = "#cba6f7";
red = "#f38ba8";
maroon = "#eba0ac";
peach = "#fab387";
yellow = "#f9e2af";
green = "#a6e3a1";
teal = "#94e2d5";
sky = "#89dceb";
sapphire = "#74c7ec";
blue = "#89b4fa";
lavender = "#b4befe";
text = "#cdd6f4";
subtext1 = "#bac2de";
subtext0 = "#a6adc8";
overlay2 = "#9399b2";
overlay1 = "#7f849c";
overlay0 = "#6c7086";
surface2 = "#585b70";
surface1 = "#45475a";
surface0 = "#313244";
base = "#1e1e2e";
mantle = "#181825";
crust = "#11111b";
};
cfg = {
layer = "top";
exclusive = true;
passthrough = false;
position = "top";
spacing = 3;
"fixed-center" = true;
ipc = true;
"margin-top" = 3;
"margin-left" = 8;
"margin-right" = 8;
modules-left = [
"custom/separator#line"
"custom/startmenu"
"custom/qs_wallpapers_apply"
"custom/separator#blank"
"custom/qs_vid_wallpapers_apply"
"custom/separator#line"
"custom/separator#blank"
"custom/cava_mviz"
"custom/separator#blank"
"custom/separator#line"
"tray"
"custom/separator#line"
];
# Remove Hyprland-specific modules for MangoWC
modules-center = [
"custom/separator#line"
"custom/playerctl"
"custom/separator#line"
];
modules-right = [
"custom/separator#line"
"custom/swaync"
"custom/separator#line"
"idle_inhibitor"
"custom/separator#line"
"clock"
"custom/separator#line"
"custom/weather"
"custom/separator#line"
"group/audio"
"custom/separator#line"
"custom/power"
];
temperature = {
interval = 10;
tooltip = true;
"hwmon-path" = [
"/sys/class/hwmon/hwmon1/temp1_input"
"/sys/class/thermal/thermal_zone0/temp"
];
"critical-threshold" = 82;
"format-critical" = "{temperatureC}°C {icon}";
format = "{temperatureC}°C {icon}";
"format-icons" = ["󰈸"];
"on-click-right" = "$HOME/.config/hypr/scripts/WaybarScripts.sh --nvtop";
};
battery = {
align = 0;
rotate = 0;
"full-at" = 100;
"design-capacity" = false;
states = {
good = 95;
warning = 30;
critical = 15;
};
format = "{icon} {capacity}%";
"format-charging" = " {capacity}%";
"format-plugged" = "󱘖 {capacity}%";
"format-alt-click" = "click";
"format-full" = "{icon} Full";
"format-alt" = "{icon} {time}";
"format-icons" = [
"󰂎"
"󰁺"
"󰁻"
"󰁼"
"󰁽"
"󰁾"
"󰁿"
"󰂀"
"󰂁"
"󰂂"
"󰁹"
];
"format-time" = "{H}h {M}min";
tooltip = true;
"tooltip-format" = "{timeTo} {power}w";
"on-click-middle" = "$HOME/.config/hypr/scripts/ChangeBlur.sh";
"on-click-right" = "$HOME/.config/hypr/scripts/Wlogout.sh";
};
bluetooth = {
format = " ";
"format-disabled" = "󰂳";
"format-connected" = "󰂱 {num_connections}";
"tooltip-format" = " {device_alias}";
"tooltip-format-connected" = "{device_enumerate}";
"tooltip-format-enumerate-connected" = " {device_alias} 󰂄{device_battery_percentage}%";
tooltip = true;
"on-click" = "blueman-manager";
};
clock = {
interval = 1;
format = " {:%I:%M %p}";
"format-alt" = " {:%H:%M %Y, %d %B, %A}";
"tooltip-format" = "<tt><small>{calendar}</small></tt>";
calendar = {
mode = "year";
"mode-mon-col" = 3;
"weeks-pos" = "right";
"on-scroll" = 1;
format = {
months = "<span color='#ffead3'><b>{}</b></span>";
days = "<span color='#ecc6d9'><b>{}</b></span>";
weeks = "<span color='#99ffdd'><b>W{:%V}</b></span>";
weekdays = "<span color='#ffcc66'><b>{}</b></span>";
today = "<span color='#ff6699'><b><u>{}</u></b></span>";
};
};
actions = {
"on-click-right" = "mode";
"on-click-forward" = "tz_up";
"on-click-backward" = "tz_down";
"on-scroll-up" = "shift_up";
"on-scroll-down" = "shift_down";
};
};
cpu = {
format = "{usage}% 󰍛";
interval = 1;
"min-length" = 5;
"format-alt-click" = "click";
"format-alt" = "{icon0}{icon1}{icon2}{icon3} {usage:>2}% 󰍛";
"format-icons" = ["" "" "" "" "" "" "" ""];
"on-click-right" = "gnome-system-monitor";
};
disk = {
interval = 30;
path = "/";
format = "{percentage_used}% 󰋊";
"tooltip-format" = "{used} used out of {total} on {path} ({percentage_used}%)";
};
idle_inhibitor = {
tooltip = true;
"tooltip-format-activated" = "Idle_inhibitor active";
"tooltip-format-deactivated" = "Idle_inhibitor not active";
format = "{icon}";
"format-icons" = {
activated = " ";
deactivated = " ";
};
};
"keyboard-state" = {
capslock = true;
format = {
numlock = "N {icon}";
capslock = "󰪛 {icon}";
};
"format-icons" = {
locked = "";
unlocked = "";
};
};
memory = {
interval = 10;
format = "{used:0.1f}G 󰾆";
"format-alt" = "{percentage}% 󰾆";
"format-alt-click" = "click";
tooltip = true;
"tooltip-format" = "{used:0.1f}GB/{total:0.1f}G";
"on-click-right" = "$HOME/.config/hypr/scripts/WaybarScripts.sh --btop";
};
network = {
format = "{ifname}";
"format-wifi" = "{icon}";
"format-ethernet" = "󰌘";
"format-disconnected" = "󰌙";
"tooltip-format" = "{ipaddr} {bandwidthUpBits} {bandwidthDownBits}";
"format-linked" = "󰈁 {ifname} (No IP)";
"tooltip-format-wifi" = "{essid} {icon} {signalStrength}%";
"tooltip-format-ethernet" = "{ifname} 󰌘";
"tooltip-format-disconnected" = "󰌙 Disconnected";
"max-length" = 30;
"format-icons" = ["󰤯" "󰤟" "󰤢" "󰤥" "󰤨"];
"on-click-right" = "$HOME/.config/hypr/scripts/WaybarScripts.sh --nmtui";
};
"network#speed" = {
interval = 1;
format = "{ifname}";
"format-wifi" = "{icon} {bandwidthUpBytes} {bandwidthDownBytes}";
"format-ethernet" = "󰌘 {bandwidthUpBytes} {bandwidthDownBytes}";
"format-disconnected" = "󰌙";
"tooltip-format" = "{ipaddr}";
"format-linked" = "󰈁 {ifname} (No IP)";
"tooltip-format-wifi" = "{essid} {icon} {signalStrength}%";
"tooltip-format-ethernet" = "{ifname} 󰌘";
"tooltip-format-disconnected" = "󰌙 Disconnected";
"min-length" = 24;
"max-length" = 24;
"format-icons" = ["󰤯" "󰤟" "󰤢" "󰤥" "󰤨"];
};
"power-profiles-daemon" = {
format = "{icon} ";
"tooltip-format" = "Power profile: {profile}\nDriver: {driver}";
tooltip = true;
"format-icons" = {
default = "";
performance = "";
balanced = "";
"power-saver" = "";
};
};
pulseaudio = {
format = "{icon} {volume}%";
"format-bluetooth" = "{icon} 󰂰 {volume}%";
"format-muted" = "󰖁";
"format-icons" = {
headphone = "";
"hands-free" = "";
headset = "";
phone = "";
portable = "";
car = "";
default = ["" "" "󰕾" ""];
"ignored-sinks" = ["Easy Effects Sink"];
};
"scroll-step" = 5.0;
"on-click" = "$HOME/.config/hypr/scripts/Volume.sh --toggle";
"on-click-right" = "pavucontrol -t 3";
"on-scroll-up" = "$HOME/.config/hypr/scripts/Volume.sh --inc";
"on-scroll-down" = "$HOME/.config/hypr/scripts/Volume.sh --dec";
"tooltip-format" = "{icon} {desc} | {volume}%";
"smooth-scrolling-threshold" = 1;
};
"pulseaudio#microphone" = {
format = "{format_source}";
"format-source" = " {volume}%";
"format-source-muted" = "";
"on-click" = "$HOME/.config/hypr/scripts/Volume.sh --toggle-mic";
"on-click-right" = "pavucontrol -t 4";
"on-scroll-up" = "$HOME/.config/hypr/scripts/Volume.sh --mic-inc";
"on-scroll-down" = "$HOME/.config/hypr/scripts/Volume.sh --mic-dec";
"tooltip-format" = "{source_desc} | {source_volume}%";
"scroll-step" = 5;
};
tray = {
"icon-size" = 20;
spacing = 4;
};
"group/audio" = {
orientation = "inherit";
drawer = {
"transition-duration" = 500;
"children-class" = "pulseaudio";
"transition-left-to-right" = true;
};
modules = ["pulseaudio" "pulseaudio#microphone"];
};
"custom/weather" = {
return-type = "json";
exec = "sh -lc 'WEATHER_ICON_STYLE=emoji WEATHER_TOOLTIP_MARKUP=1 ~/.config/mangowc/waybar/scripts/Weather.py'";
interval = 600;
tooltip = true;
};
"custom/file_manager" = {
format = " ";
"on-click" = "$HOME/.config/hypr/scripts/WaybarScripts.sh --files";
tooltip = true;
"tooltip-format" = "File Manager";
};
"custom/tty" = {
format = " ";
"on-click" = "$HOME/.config/hypr/scripts/WaybarScripts.sh --term";
tooltip = true;
"tooltip-format" = "Launch Terminal";
};
"custom/browser" = {
format = " ";
"on-click" = "xdg-open https://";
tooltip = true;
"tooltip-format" = "Launch Browser";
};
"custom/settings" = {
format = " ";
"on-click" = "$HOME/.config/hypr/scripts/Kool_Quick_Settings.sh";
tooltip = true;
"tooltip-format" = "Launch KooL Settings Menu";
};
"custom/qs_wallpapers_apply" = {
format = " ";
"on-click" = "qs-wallpapers-apply";
tooltip = true;
"tooltip-format" = "Set wallpaper";
};
"custom/qs_vid_wallpapers_apply" = {
format = " ";
"on-click" = "qs-vid-wallpapers-apply";
tooltip = true;
"tooltip-format" = "Set video wallpaper";
};
"custom/light_dark" = {
format = "󰔎 ";
"on-click" = "$HOME/.config/hypr/scripts/DarkLight.sh";
"on-click-right" = "$HOME/.config/hypr/scripts/WaybarStyles.sh";
tooltip = true;
"tooltip-format" = "Left Click: Switch Dark-Light Themes\nRight Click: Waybar Styles Menu";
};
"custom/lock" = {
format = "󰌾";
"on-click" = "$HOME/.config/hypr/scripts/LockScreen.sh";
tooltip = true;
"tooltip-format" = "󰷛 Screen Lock";
};
"custom/menu" = {
format = " ";
"on-click" = "launch-nwg-menu";
"on-click-right" = "$HOME/.config/hypr/scripts/WaybarLayout.sh";
tooltip = true;
"tooltip-format" = "Left Click: App Menu\nRight Click: Waybar Layout Menu";
};
"custom/startmenu" = {
tooltip = true;
"tooltip-format" = "App menu";
format = "";
on-click = "launch-nwg-menu";
};
"custom/cava_mviz" = {
exec = "${waybarCava}/bin/WaybarCava";
format = "<span color='#a6e3a1'>[</span> {} <span color='#a6e3a1'>]</span>";
};
"custom/playerctl" = {
format = "<span>{}</span>";
"return-type" = "json";
"max-length" = 25;
exec = "playerctl -a metadata --format '{\"text\": \"{{artist}} {{markup_escape(title)}}\", \"tooltip\": \"{{playerName}} : {{markup_escape(title)}}\", \"alt\": \"{{status}}\", \"class\": \"{{status}}\"}' -F";
"on-click-middle" = "playerctl play-pause";
"on-click" = "playerctl previous";
"on-click-right" = "playerctl next";
"scroll-step" = 5.0;
"on-scroll-up" = "$HOME/.config/hypr/scripts/Volume.sh --inc";
"on-scroll-down" = "$HOME/.config/hypr/scripts/Volume.sh --dec";
"smooth-scrolling-threshold" = 1;
};
"custom/power" = {
format = " ";
"on-click" = "qs-wlogout";
"on-click-right" = "~/.config/mangowc/waybar/scripts/power-menu.sh";
tooltip = true;
"tooltip-format" = "Power menu: Left-click for QS logout, Right-click for rofi power menu";
};
"custom/quit" = {
format = "󰗼";
"on-click" = "loginctl kill-session $XDG_SESSION_ID";
tooltip = true;
"tooltip-format" = "Left Click: Logout";
};
"custom/swaync" = {
tooltip = true;
"tooltip-format" = "Left Click: Launch Notification Center\nRight Click: Do not Disturb";
format = "{} {icon} ";
"format-icons" = {
notification = "<span foreground='red'><sup></sup></span>";
none = "";
"dnd-notification" = "<span foreground='red'><sup></sup></span>";
"dnd-none" = "";
"inhibited-notification" = "<span foreground='red'><sup></sup></span>";
"inhibited-none" = "";
"dnd-inhibited-notification" = "<span foreground='red'><sup></sup></span>";
"dnd-inhibited-none" = "";
};
"return-type" = "json";
"exec-if" = "which swaync-client";
exec = "swaync-client -swb";
"on-click" = "systemctl --user start swaync.service; swaync-client -t";
"on-click-right" = "systemctl --user start swaync.service; swaync-client -d";
escape = true;
};
"custom/separator#line" = {
format = "|";
interval = "once";
tooltip = false;
};
"custom/separator#blank" = {
format = "";
interval = "once";
tooltip = false;
};
};
style = let
c = catppuccinColors;
in ''
@define-color rosewater ${c.rosewater};
@define-color flamingo ${c.flamingo};
@define-color pink ${c.pink};
@define-color mauve ${c.mauve};
@define-color red ${c.red};
@define-color maroon ${c.maroon};
@define-color peach ${c.peach};
@define-color yellow ${c.yellow};
@define-color green ${c.green};
@define-color teal ${c.teal};
@define-color sky ${c.sky};
@define-color sapphire ${c.sapphire};
@define-color blue ${c.blue};
@define-color lavender ${c.lavender};
@define-color text ${c.text};
@define-color subtext1 ${c.subtext1};
@define-color subtext0 ${c.subtext0};
@define-color overlay2 ${c.overlay2};
@define-color overlay1 ${c.overlay1};
@define-color overlay0 ${c.overlay0};
@define-color surface2 ${c.surface2};
@define-color surface1 ${c.surface1};
@define-color surface0 ${c.surface0};
@define-color base ${c.base};
@define-color mantle ${c.mantle};
@define-color crust ${c.crust};
* { font-family: "JetBrainsMono Nerd Font"; font-weight: bold; min-height: 0; font-size: 101%; }
window#waybar { background-color: @base; border-radius: 5px; }
tooltip { background: @base; opacity: 1; border-radius: 10px; border-width: 2px; border-style: solid; border-color: @sapphire; }
tooltip label { color: @blue; }
#custom-startmenu { color: @green; margin-left: 4px; margin-right: 8px; }
#custom-cava_mviz { margin-left: 4px; }
#workspaces button { color: @sapphire; }
#workspaces button.active { color: @green; }
#workspaces button.empty { color: @red; }
#idle_inhibitor.activated { color: @green; }
#idle_inhibitor.deactivated { color: @red; }
#custom-power { color: @red; }
#network { color: @mauve; }
#custom-weather { color: @green; }
#pulseaudio { color: @blue; }
#clock { color: @green; }
#custom-playerctl { color: @lavender; }
'';
mangowcWaybar = pkgs.writeShellScriptBin "mangowc-waybar" ''
exec waybar -c "$HOME/.config/mangowc/waybar/config.jsonc" -s "$HOME/.config/mangowc/waybar/style.css"
'';
in {
# Install scripts under ~/.config/mangowc/waybar/scripts
home.file =
builtins.listToAttrs (
map (name: {
name = ".config/mangowc/waybar/scripts/" + name;
value = {
source = "${scriptsDir}/${name}";
executable = true;
};
})
scripts
)
// {
".config/mangowc/waybar/config.jsonc".text = builtins.toJSON [cfg];
".config/mangowc/waybar/style.css".text = style;
};
home.packages = [mangowcWaybar waybarCava];
}

View File

@@ -0,0 +1,499 @@
{
pkgs,
lib,
...
}: let
# Install helper scripts shipped in modules/home/waybar/scripts into ~/.config/waybar/scripts
scriptsDir = ./scripts;
scripts = builtins.attrNames (builtins.readDir scriptsDir);
# Updated CAVA visualizer script packaged via Nix (from waybar-jak-catppuccin.nix)
waybarCava = pkgs.writeShellScriptBin "WaybarCava" ''
set -euo pipefail
# Ensure cava exists
if ! command -v cava >/dev/null 2>&1; then
echo "cava not found in PATH" >&2
exit 1
fi
# Characters for vertical bars (0..7)
bar=""
# Build sed script that maps 0..7 to glyphs and strips ';'
dict="s/;//g"
bar_length=''${#bar}
for ((i = 0; i < bar_length; i++)); do
dict+=";s/$i/''${bar:$i:1}/g"
done
RUNTIME_DIR="''${XDG_RUNTIME_DIR:-/tmp}"
pidfile="$RUNTIME_DIR/waybar-cava.pid"
if [ -f "$pidfile" ]; then
oldpid=$(cat "$pidfile" || true)
if [ -n "''${oldpid:-}" ] && kill -0 "$oldpid" 2>/dev/null; then
kill "$oldpid" 2>/dev/null || true
sleep 0.1 || true
fi
fi
echo $$ > "$pidfile"
config_file=$(mktemp "''${RUNTIME_DIR}/waybar-cava.XXXXXX.conf")
cleanup() {
rm -f "$config_file" "$pidfile"
}
trap cleanup EXIT INT TERM
cat >"$config_file" <<EOF
[general]
framerate = 30
bars = 10
[input]
method = pulse
source = auto
[output]
method = raw
raw_target = /dev/stdout
data_format = ascii
ascii_max_range = 7
EOF
exec cava -p "$config_file" | sed -u "$dict"
'';
in
with lib; {
home.file = builtins.listToAttrs (
map (name: {
name = ".config/waybar/scripts/" + name;
value = {
source = "${scriptsDir}/${name}";
executable = true;
};
})
scripts
);
programs.waybar = {
enable = true;
package = pkgs.waybar;
settings = [
{
layer = "top";
position = "top";
spacing = 6;
# Old ddubsOS layout converted, with updated CAVA and tweaks
modules-center = ["hyprland/workspaces"];
modules-left = [
"custom/startmenu"
"group/qs_wallpapers"
"clock"
"hyprland/window"
"custom/sep_left"
"custom/cava_mviz"
"custom/sep_right"
"tray"
];
modules-right = [
"custom/weather"
"idle_inhibitor"
"custom/notification"
"pulseaudio"
"cpu"
"memory"
"battery"
"custom/power"
];
"hyprland/workspaces" = {
format = "{name}";
format-icons = {
default = " ";
active = " ";
urgent = " ";
};
on-scroll-up = "hyprctl dispatch workspace e+1";
on-scroll-down = "hyprctl dispatch workspace e-1";
};
clock = {
format = " {:%H:%M}";
tooltip = true;
tooltip-format = "<big>{:%A, %d.%B %Y }</big><tt><small>{calendar}</small></tt>";
};
"hyprland/window" = {
max-length = 25;
separate-outputs = false;
};
memory = {
interval = 5;
format = " {}%";
tooltip = true;
};
cpu = {
interval = 5;
format = " {usage:2}%";
tooltip = true;
};
disk = {
format = " {free}";
tooltip = true;
};
network = {
format-icons = [
"󰤯"
"󰤟"
"󰤢"
"󰤥"
"󰤨"
];
format-ethernet = " {bandwidthDownOctets}";
format-wifi = "{icon} {signalStrength}%";
format-disconnected = "󰤮";
tooltip = false;
};
tray = {
spacing = 12;
};
pulseaudio = {
format = "{icon} {volume}% {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-icons = {
headphone = "";
hands-free = "";
headset = "";
phone = "";
portable = "";
car = "";
default = [
""
""
""
];
};
on-click = "sleep 0.1 && pavucontrol";
};
# Updated CAVA visualizer
"custom/cava_mviz" = {
exec = "${waybarCava}/bin/WaybarCava";
format = "{}";
};
# Separators
"custom/sep_left" = {
format = " [";
tooltip = false;
};
"custom/sep_right" = {
format = "] ";
tooltip = false;
};
"custom/startmenu" = {
tooltip = true;
tooltip-format = "App menu";
format = "";
on-click = "rofi-legacy.menu";
"on-click-right" = "nwg-drawer -mr 225 -ml 225 -mt 200 -mb 200 -is 48 --spacing 15";
#on-click = "rofi -show drun";
};
# Wallpaper quick-switchers (same color/background as startmenu)
"custom/qs_wallpapers_apply" = {
format = "";
tooltip = true;
tooltip-format = "Set image wallpaper";
on-click = "qs-wallpapers-apply";
};
"custom/qs_vid_wallpapers_apply" = {
format = "";
tooltip = true;
tooltip-format = "Set video wallpaper";
on-click = "qs-vid-wallpapers-apply";
};
# Group the QS buttons to avoid bar spacing between them
"group/qs_wallpapers" = {
orientation = "inherit";
modules = [
"custom/qs_wallpapers_apply"
"custom/qs_vid_wallpapers_apply"
];
};
# Notifications (compatible with swaync if present)
"custom/notification" = {
tooltip = false;
format = "{icon} {}";
format-icons = {
notification = "<span foreground='red'><sup></sup></span>";
none = "";
dnd-notification = "<span foreground='red'><sup></sup></span>";
dnd-none = "";
inhibited-notification = "<span foreground='red'><sup></sup></span>";
inhibited-none = "";
dnd-inhibited-notification = "<span foreground='red'><sup></sup></span>";
dnd-inhibited-none = "";
};
return-type = "json";
exec-if = "which swaync-client";
exec = "swaync-client -swb";
on-click = "sleep 0.1 && task-waybar";
escape = true;
};
# Idle inhibitor with colored coffee mug icons (green active, red inactive)
idle_inhibitor = {
format = "{icon}";
format-icons = {
activated = "<span foreground='#00f769'></span>";
deactivated = "<span foreground='#ea51b2'></span>";
};
tooltip = true;
};
"custom/weather" = {
return-type = "json";
exec = "sh -lc 'WEATHER_ICON_STYLE=emoji WEATHER_TOOLTIP_MARKUP=1 ~/.local/bin/weather'";
interval = 600;
tooltip = true;
};
battery = {
states = {
warning = 30;
critical = 15;
};
format = "{icon} {capacity}%";
format-charging = "󰂄 {capacity}%";
format-plugged = "󱘖 {capacity}%";
format-icons = [
"󰁺"
"󰁻"
"󰁼"
"󰁽"
"󰁾"
"󰁿"
"󰂀"
"󰂁"
"󰂂"
"󰁹"
];
on-click = "";
tooltip = false;
};
# Power menu with qs-wlogout primary and rofi fallback (icon: ⏻)
"custom/power" = {
tooltip = true;
"tooltip-format" = "Power menu: Left-click for QS logout, Right-click for rofi power menu";
format = " ";
"on-click" = "qs-wlogout";
"on-click-right" = "~/.config/waybar/scripts/power-menu.sh";
};
}
];
style = concatStrings [
''
* {
font-size: 16px;
font-family: JetBrainsMono Nerd Font, Font Awesome, sans-serif;
font-weight: bold;
}
window#waybar {
background-color: #282936;
border-bottom: 1px solid #282936;
border-radius: 0px;
color: #00f769;
}
#workspaces {
background: #3a3c4e;
margin: 4px;
padding: 0px 1px;
border-radius: 10px;
border: 0px;
font-style: normal;
color: #282936;
}
#workspaces button {
padding: 0px 5px;
margin: 4px 3px;
border-radius: 10px;
border: 0px;
color: #282936;
background: linear-gradient(45deg, #b45bcf, #00f769, #62d6e8, #b45bcf);
background-size: 300% 300%;
animation: gradient_horizontal 15s ease infinite;
opacity: 0.5;
transition: all 0.3s cubic-bezier(.55,-0.68,.48,1.682);
}
#workspaces button.active {
padding: 0px 5px;
margin: 4px 3px;
border-radius: 10px;
border: 0px;
color: #282936;
background: linear-gradient(45deg, #b45bcf, #00f769, #62d6e8, #b45bcf);
background-size: 300% 300%;
animation: gradient_horizontal 15s ease infinite;
transition: all 0.3s cubic-bezier(.55,-0.68,.48,1.682);
opacity: 1.0;
min-width: 40px;
}
#workspaces button:hover {
border-radius: 10px;
color: #282936;
background: linear-gradient(45deg, #b45bcf, #00f769, #62d6e8, #b45bcf);
background-size: 300% 300%;
animation: gradient_horizontal 15s ease infinite;
opacity: 0.8;
transition: all 0.3s cubic-bezier(.55,-0.68,.48,1.682);
}
@keyframes gradient_horizontal {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes swiping {
0% { background-position: 0% 200%; }
100% { background-position: 200% 200%; }
}
tooltip {
background: #282936;
border: 1px solid #b45bcf;
border-radius: 10px;
}
tooltip label { color: #f7f7fb; }
#window {
margin: 4px;
padding: 2px 10px;
color: #62d6e8; /* match pulseaudio blue */
background: #3a3c4e;
border-radius: 10px;
}
#memory {
color: #00f769;
background: #3a3c4e;
margin: 4px;
padding: 2px 10px;
border-radius: 10px;
}
#clock {
color: #ebff87;
background: #3a3c4e;
margin: 4px;
padding: 2px 10px;
border-radius: 10px;
}
#cpu {
color: #f7f7fb;
background: #3a3c4e;
margin: 4px;
padding: 2px 10px;
border-radius: 10px;
}
#disk {
color: #626483;
background: #3a3c4e;
margin: 4px;
padding: 2px 10px;
border-radius: 10px;
}
#battery {
color: #ea51b2;
background: #3a3c4e;
margin: 4px;
padding: 2px 10px;
border-radius: 10px;
}
#network {
color: #b45bcf;
background: #3a3c4e;
margin: 4px;
padding: 2px 10px;
border-radius: 10px;
}
#custom-hyprbindings {
color: #b45bcf;
background: #3a3c4e;
margin: 4px;
padding: 2px 10px;
border-radius: 10px;
}
#tray {
color: #e9e9f4;
background: #3a3c4e;
margin: 4px;
padding: 2px 10px;
border-radius: 10px;
}
#pulseaudio {
color: #62d6e8;
background: #3a3c4e;
margin: 4px;
padding: 2px 10px;
border-radius: 10px;
}
#custom-notification {
color: #00f769; /* default green like jak-catppuccin */
background: #3a3c4e;
margin: 4px;
padding: 2px 8px;
border-radius: 10px;
}
/* Turn red on new alerts (match class names used by format-icons) */
#custom-notification.notification,
#custom-notification.dnd-notification,
#custom-notification.inhibited-notification {
color: #f38ba8; /* catppuccin red tone */
}
#custom-startmenu {
color: #00f769;
background: transparent;
margin: 2px;
padding: 3px 5px;
border-radius: 10px;
}
/* Match qs icons to startmenu color/background */
#custom-qs_wallpapers_apply,
#custom-qs_vid_wallpapers_apply {
color: #00f769;
background: transparent;
margin: 7px;
padding: 3px 8px;
border-radius: 10px;
}
/* Inside the QS group, make the two buttons sit closer */
#group-qs_wallpapers { margin: 0; padding: 0; }
#group-qs_wallpapers > * { margin: 0; }
/* Tighten QS buttons margins/padding to bring them closer */
#custom-qs_wallpapers_apply,
#custom-qs_vid_wallpapers_apply {
margin: 2px 2px;
padding: 2px 4px;
}
#idle_inhibitor {
color: #b45bcf;
background: #3a3c4e;
margin: 4px 0px;
padding: 2px 14px;
border-radius: 10px;
}
#custom-power {
color: #ea51b2; /* Red color for power menu */
background: #3a3c4e;
margin: 4px 4px;
padding: 10px 10px;
border-radius: 10px;
}
''
];
};
}

View File

@@ -0,0 +1,340 @@
{
pkgs,
lib,
host,
config,
...
}: let
# Keep consistency with other modules; scriptsDir can supply Weather.py, etc.
scriptsDir = ./scripts;
scripts = builtins.attrNames (builtins.readDir scriptsDir);
in
with lib; {
# Install any helper scripts from ./scripts into ~/.config/waybar/scripts
home.file = builtins.listToAttrs (
map
(name: {
name = ".config/waybar/scripts/" + name;
value = {
source = "${scriptsDir}/${name}";
executable = true;
};
})
scripts
);
programs.waybar = {
enable = true;
package = pkgs.waybar;
# Waybar configuration
settings = [
{
layer = "top";
position = "top";
height = 30;
spacing = 4;
# Layout to mirror waybarpc/config (with requested removals)
# - Left: start menu, clock, separator, weather
# - Center: workspaces
# - Right: tray, pulseaudio, battery, swaync, idle_inhibitor, power
modules-left = [
"custom/startmenu"
"clock"
"custom/weather"
];
modules-center = [
"hyprland/workspaces"
];
modules-right = [
"battery"
"tray"
"custom/notification"
"idle_inhibitor"
"pulseaudio"
"custom/power"
];
# Workspaces (Hyprland)
"hyprland/workspaces" = {
format = "{name}";
format-icons = {
default = " ";
active = " ";
urgent = " ";
};
on-scroll-up = "hyprctl dispatch workspace e+1";
on-scroll-down = "hyprctl dispatch workspace e-1";
};
# 12-hour time format
clock = {
format = '' {:%I:%M %p}'';
tooltip = true;
tooltip-format = "<big>{:%A, %d.%B %Y }</big>\n<tt><small>{calendar}</small></tt>";
};
# Idle inhibitor with coffee mug icon
idle_inhibitor = {
format = "{icon}";
format-icons = {
activated = "";
deactivated = "";
};
tooltip = true;
};
# Notifications (SwayNotificationCenter via swaync-client)
"custom/notification" = {
tooltip = false;
format = "{icon}";
format-icons = {
notification = "<span foreground='red'><sup></sup></span>";
none = "";
dnd-notification = "<span foreground='red'><sup></sup></span>";
dnd-none = "";
inhibited-notification = "<span foreground='red'><sup></sup></span>";
inhibited-none = "";
dnd-inhibited-notification = "<span foreground='red'><sup></sup></span>";
dnd-inhibited-none = "";
};
return-type = "json";
exec-if = "which swaync-client";
exec = "swaync-client -swb";
on-click = "swaync-client -t";
escape = true;
};
# Audio
pulseaudio = {
format = "{icon} {volume}% {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-icons = {
headphone = "";
hands-free = "";
headset = "";
phone = "";
portable = "";
car = "";
default = ["" "" ""];
};
on-click = "sleep 0.1 && pavucontrol";
};
# Battery
battery = {
states = {
warning = 30;
critical = 15;
};
format = "{icon} {capacity}%";
format-charging = "󰂄 {capacity}%";
format-plugged = "󱘖 {capacity}%";
format-icons = [
"󰁺"
"󰁻"
"󰁼"
"󰁽"
"󰁾"
"󰁿"
"󰂀"
"󰂁"
"󰂂"
"󰁹"
];
tooltip = false;
};
# Tray
tray = {
spacing = 10;
};
# Start menu and power menu
"custom/startmenu" = {
format = "";
tooltip = true;
"tooltip-format" = "App menu";
# on-click = "rofi -show drun";
on-click = "launch-nwg-menu";
"on-click-right" = "nwg-drawer -mr 225 -ml 225 -mt 200 -mb 200 -is 48 --spacing 15";
};
"custom/power" = {
tooltip = true;
"tooltip-format" = "Power menu: Left-click for QS logout";
format = " ";
on-click = "qs-wlogout";
};
# Weather widget (uses existing ddubsos Weather.py script if present)
"custom/weather" = {
return-type = "json";
exec = "sh -lc 'WEATHER_ICON_STYLE=emoji WEATHER_TOOLTIP_MARKUP=1 ~/.local/bin/weather'";
interval = 600;
tooltip = true;
};
}
];
# Styling to mirror ~/Projects/waybarpc/style.css (with Catppuccin-like colors)
style = lib.concatStrings [
''
/* Color variables based on waybarpc/colors.css */
@define-color bg #1a1b26;
@define-color bg-alt #16161e;
@define-color bg-alt2 #24283b;
@define-color bg-alt3 #414868;
@define-color border #565f89;
@define-color border2 #6183bb;
@define-color text #c0caf5;
@define-color text-dark #1a1b26; /* alias for typo tex-dark in original */
@define-color tex-dark #1a1b26;
@define-color accent #7e9cd8;
@define-color accent2 #957fb8;
@define-color red #f7768e;
@define-color green #a6e3a1;
@define-color blue #89b4fa;
* {
font-family: JetBrainsMono Nerd Font, monospace;
font-size: 13px;
}
window#waybar { background-color: @bg; }
window#waybar.hidden { opacity: 0.5; margin: 15px; }
/* Workspaces container */
#workspaces {
background-color: @bg-alt;
padding: 2px;
margin: 2px;
border-radius: 10px;
}
/* 3D shaded workspace buttons */
#workspaces button {
all: initial;
min-width: 0;
background: linear-gradient(to bottom, @bg-alt3, @bg);
padding: 6px 12px;
transition: all 0.1s linear;
border-top: 1px solid @border;
box-shadow: 0px 17px 5px 1px rgba(0, 0, 0, 0.2);
color: @accent2;
font-weight: 800;
text-shadow: -1px -1px 1px rgba(205, 214, 244, 0.1), 0px 2px 3px rgba(0, 0, 0, 0.3);
}
#workspaces button:first-child {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
#workspaces button:last-child {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
#workspaces button.empty {
color: #737373;
background: transparent;
text-shadow: none;
box-shadow: none;
}
#workspaces button.active {
box-shadow: 0px 20px 8px 2px rgba(0, 0, 0, 0.35);
background: linear-gradient(to bottom, @bg-alt3, @bg-alt);
color: @text;
font-size: 25px;
border-top: 1px solid @border2;
border-bottom: 3px solid @border2;
text-shadow: 0px 0px 14px @text;
}
#workspaces button.focused {
background: linear-gradient(to bottom, @accent2, @bg-alt3);
color: @bg;
text-shadow: 0px 0px 12px @accent2;
font-size: 22px;
border-top: 1px solid @accent2;
}
#workspaces button.visible {
background: linear-gradient(to bottom, @bg-alt3, @bg-alt2);
color: @accent;
text-shadow: none;
font-size: 18px;
border-top: 1px solid @border;
}
#workspaces button.urgent {
background-color: @red;
color: @bg;
text-shadow: 0px 0px 8px @red;
font-weight: bold;
}
/* Clock with gradient block */
#clock {
background: linear-gradient(to bottom, @bg-alt3, @bg);
font-size: 14px;
margin: 2px;
padding: 6px 10px;
border-radius: 6px;
font-weight: bold;
border-top: 1px solid @border;
border-bottom: 2px solid @bg-alt;
color: @text;
}
/* Notifications state color cues */
#custom-notification.none { color: @green; }
#custom-notification.notification, #custom-notification.dnd-notification, #custom-notification.inhibited-notification, #custom-notification.dnd-inhibited-notification { color: @red; }
/* Idle inhibitor colors */
#idle_inhibitor.activated { color: @green; }
#idle_inhibitor.deactivated { color: @red; }
/* Right-side blocks */
#pulseaudio, #battery, #tray {
background: linear-gradient(to bottom, @bg-alt3, @bg);
padding: 4px 8px;
margin: 2px;
border-radius: 6px;
border-top: 1px solid @border;
border-bottom: 2px solid @bg-alt;
font-weight: bold;
color: @text;
}
#pulseaudio { margin-left: 0; margin-right: 0; }
/* Shaded block style (match workspace button look) */
#custom-startmenu, #custom-weather, #idle_inhibitor, #custom-power, #custom-notification {
background: linear-gradient(to bottom, @bg-alt3, @bg);
padding: 6px 10px;
margin: 2px;
border-radius: 6px;
border-top: 1px solid @border;
border-bottom: 2px solid @bg-alt;
box-shadow: 0px 12px 4px 1px rgba(0, 0, 0, 0.2);
text-shadow: -1px -1px 1px rgba(205, 214, 244, 0.1), 0px 2px 3px rgba(0, 0, 0, 0.3);
color: @text;
font-size: 14px;
}
/* Make icons slightly larger for clarity */
#custom-startmenu, #idle_inhibitor, #custom-power, #custom-notification { font-size: 16px; }
#battery.warning, #battery.critical, #battery.urgent { color: @red; }
/* Separator styling */
#custom-sep { color: @accent; }
/* Center the startmenu glyph like the power button */
#custom-startmenu {
min-width: 28px;
min-height: 24px;
padding-left: 8px;
padding-right: 12px;
}
''
];
};
}

View File

@@ -0,0 +1,449 @@
{
pkgs,
lib,
host,
config,
...
}: let
inherit (import ../../../hosts/${host}/variables.nix) clock24h;
scriptsDir = ./scripts;
scripts = builtins.attrNames (builtins.readDir scriptsDir);
in
with lib; {
# Configure & Theme Waybar (Tony)
home.file = builtins.listToAttrs (
map (name: {
name = ".config/waybar/scripts/" + name;
value = {
source = "${scriptsDir}/${name}";
executable = true;
};
})
scripts
);
programs.waybar = {
enable = true;
package = pkgs.waybar;
settings = [
{
layer = "top";
position = "top";
height = 30;
spacing = 4;
modules-left = [
"custom/startmenu"
"custom/sep_gap_left"
"group/qs_wallpapers"
"custom/sep"
"hyprland/workspaces"
"custom/sep"
#"custom/sep"
"hyprland/window"
"custom/sep"
];
modules-center = [
#"custom/sep"
"idle_inhibitor"
"custom/weather"
"custom/notification"
#"custom/sep"
];
modules-right = [
"custom/sep"
"tray"
"custom/sep"
"pulseaudio"
#"network"
"cpu"
#"memory"
"clock"
"custom/sep"
"custom/power"
];
"hyprland/workspaces" = {
disable-scroll = true;
all-outputs = true;
warp-on-scroll = false;
format = "{name}";
persistent-workspaces = {
"*" = 9;
};
};
"hyprland/window" = {
max-length = 40;
seperate-outputs = false;
};
tray = {
spacing = 10;
};
clock = {
format-alt = "{:%Y-%m-%d}";
};
cpu = {
format = "CPU: {usage}%";
tooltip = false;
};
memory = {
format = "Mem: {used}GiB";
};
disk = {
interval = 60;
path = "/";
format = "Disk: {free}";
};
battery = {
states = {
good = 95;
warning = 30;
critical = 15;
};
format = "Bat: {capacity}% {icon} {time}";
format-plugged = "{capacity}% ";
format-alt = "Bat {capacity}%";
format-time = "{H}:{M}";
format-icons = [
""
""
""
""
""
];
};
network = {
format-icons = [
"󰤯"
"󰤟"
"󰤢"
"󰤥"
"󰤨"
];
format-ethernet = "{ifname} {ipaddr}";
format-wifi = "{essid} {signalStrength}% {ipaddr}";
format-disconnected = "󰤮";
tooltip = true;
tooltip-format = "{ifname}\nIPv4: {ipaddr}/{cidr}\nGateway: {gwaddr}\nSSID: {essid}\nSignal: {signalStrength}%";
on-click = "nmtui";
};
"custom/sep" = {
format = "|";
interval = 0;
};
"idle_inhibitor" = {
format = "{icon}";
format-icons = {
activated = "";
deactivated = "";
};
tooltip = true;
};
"custom/startmenu" = {
format = "";
tooltip = true;
"tooltip-format" = "App menu";
on-click = "rofi -show drun";
"on-click-right" = "nwg-drawer -mr 225 -ml 225 -mt 200 -mb 200 -is 48 --spacing 15";
};
# Wallpaper quick-switchers close to startmenu
"custom/qs_wallpapers_apply" = {
format = "";
tooltip = true;
"tooltip-format" = "Set image wallpaper";
on-click = "qs-wallpapers-apply";
};
"custom/qs_vid_wallpapers_apply" = {
format = "";
tooltip = true;
"tooltip-format" = "Set video wallpaper";
on-click = "qs-vid-wallpapers-apply";
};
"group/qs_wallpapers" = {
orientation = "inherit";
modules = [
"custom/qs_wallpapers_apply"
"custom/sep_gap"
"custom/qs_vid_wallpapers_apply"
];
};
# Narrow spacer for QS icons
"custom/sep_gap" = {
format = " ";
tooltip = false;
};
# Spacer between startmenu and first QS icon
"custom/sep_gap_left" = {
format = " ";
tooltip = false;
};
"custom/notification" = {
tooltip = false;
format = "{icon} {}";
format-icons = {
notification = "<span foreground='red'><sup></sup></span>";
none = "";
dnd-notification = "<span foreground='red'><sup></sup></span>";
dnd-none = "";
inhibited-notification = "<span foreground='red'><sup></sup></span>";
inhibited-none = "";
dnd-inhibited-notification = "<span foreground='red'><sup></sup></span>";
dnd-inhibited-none = "";
};
return-type = "json";
exec-if = "which swaync-client";
exec = "swaync-client -swb";
on-click = "swaync-client -t";
escape = true;
};
"custom/weather" = {
return-type = "json";
exec = "sh -lc 'WEATHER_ICON_STYLE=emoji WEATHER_TOOLTIP_MARKUP=1 ~/.local/bin/weather'";
interval = 600;
tooltip = true;
};
pulseaudio = {
format = "{icon} {volume}% {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-icons = {
headphone = "";
hands-free = "";
headset = "";
phone = "";
portable = "";
car = "";
default = [
""
""
""
];
};
on-click = "pavucontrol";
};
"custom/power" = {
format = " ";
tooltip = true;
"tooltip-format" = "Power menu: Left-click for QS logout, Right-click for rofi power menu";
"on-click" = "qs-wlogout";
"on-click-right" = "~/.config/waybar/scripts/power-menu.sh";
};
}
];
style = concatStrings [
''
@define-color bg #1a1b26;
@define-color fg #a9b1d6;
@define-color blk #32344a;
@define-color red #f7768e;
@define-color grn #9ece6a;
@define-color ylw #e0af68;
@define-color blu #7aa2f7;
@define-color mag #ad8ee6;
@define-color cyn #0db9d7;
@define-color brblk #444b6a;
@define-color white #ffffff;
* {
font-family: "JetBrainsMono Nerd Font", monospace;
font-size: 16px;
font-weight: bold;
}
window#waybar {
background-color: @bg;
color: @fg;
}
#workspaces button {
padding: 0 6px;
color: @cyn;
background: transparent;
border-bottom: 3px solid @bg;
}
#workspaces button.active {
color: @cyn;
border-bottom: 3px solid @mag;
}
#workspaces button.empty {
color: @white;
}
#workspaces button.empty.active {
color: @cyn;
border-bottom: 3px solid @mag;
}
#workspaces button.urgent {
background-color: @red;
}
button:hover {
background: inherit;
box-shadow: inset 0 -3px #ffffff;
}
#clock,
#custom-sep,
#battery,
#cpu,
#memory,
#disk,
#network,
#tray,
#pulseaudio,
#idle_inhibitor,
#custom-notification,
#custom-power {
padding: 0 8px;
color: @white;
}
/* Rounded, raised bottom effect for center/right modules */
#clock,
#battery,
#cpu,
#memory,
#disk,
#network,
#tray,
#pulseaudio,
#idle_inhibitor,
#custom-notification,
#custom-power {
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
box-shadow: inset 0 -2px rgba(255,255,255,0.07);
}
/* Tighten spacing between the two QS buttons */
#group-qs_wallpapers { margin: 0; padding: 0; }
/* Make QS icons match startmenu color and background (bright green) */
/* Start menu stays bright green; QS icons switch to clock blue (@cyn) */
#custom-startmenu {
color: @grn;
background: transparent;
font-size: 21px; /* ~30% larger than base 16px */
}
#custom-qs_wallpapers_apply,
#custom-qs_vid_wallpapers_apply {
color: @cyn; /* match clock color */
background: transparent;
font-size: 21px; /* ~30% larger than base 16px */
}
/* Prevent QS icons from being clipped; add a tiny internal pad */
#custom-qs_wallpapers_apply,
#custom-qs_vid_wallpapers_apply {
padding: 0 2px;
min-width: 20px;
}
#custom-startmenu { margin-right: 6px; }
#custom-sep {
color: @brblk;
}
/* Width for gap spacer between QS icons */
#custom-sep_gap { padding: 0 1px; }
#custom-sep_gap_left { padding: 0 2px; }
#clock {
color: @cyn;
border-bottom: 4px solid @cyn;
}
#battery {
color: @mag;
border-bottom: 4px solid @mag;
}
#disk {
color: @ylw;
border-bottom: 4px solid @ylw;
}
#memory {
color: @mag;
border-bottom: 4px solid @mag;
}
#cpu {
color: @grn;
border-bottom: 4px solid @grn;
}
#network {
color: @blu;
border-bottom: 4px solid @blu;
}
#network.disconnected {
background-color: @red;
}
#pulseaudio {
color: @blu;
border-bottom: 4px solid @blu;
}
/* Center the idle_inhibitor icon within its pill */
#idle_inhibitor {
/* Center visually via symmetric padding + fixed min width */
padding-left: 10px;
padding-right: 10px;
min-width: 26px; /* keep a consistent hit area */
border-bottom: 4px solid @brblk; /* default raised strip */
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
#idle_inhibitor label {
margin: 0; /* remove any label offset */
padding: 0;
}
#idle_inhibitor.deactivated {
color: @red;
border-bottom: 4px solid @red;
}
#idle_inhibitor.activated {
color: @grn;
border-bottom: 4px solid @grn;
margin-right: 5px;
}
#custom-notification {
color: @grn; /* default green */
border-bottom: 4px solid @grn;
}
/* Turn red on new alerts */
#custom-notification.notification,
#custom-notification.dnd-notification,
#custom-notification.inhibited-notification {
color: @red;
border-bottom: 4px solid @red;
}
/* Add subtle raised effect to center modules too */
#custom-notification,
#idle_inhibitor {
box-shadow: inset 0 -3px @brblk;
}
#tray {
background-color: @bg; /* match bar background */
border-bottom: 4px solid @brblk; /* restore raised strip */
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
#custom-power {
color: @red;
border-bottom: 4px solid @red;
}
''
];
};
}

View File

@@ -10,7 +10,8 @@
./hypr/hyprland.nix
./hypr/hyprlock.nix
./hypr/theme.nix
./hypr/waybar.nix
# ./hypr/waybar.nix
./hypr/waybar/waybar-ddubs-2.nix
./hypr/wlogout.nix
]
# macOS programs

View File

@@ -1,7 +1,3 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running 'nixos-help').
{
config,
pkgs,

View File

@@ -1,7 +1,3 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running 'nixos-help').
{
config,
pkgs,

View File

@@ -1,7 +1,3 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running nixos-help).
{
config,
pkgs,
@@ -31,7 +27,12 @@
# Nix optimizations
nix.optimise.automatic = true;
nix.settings.auto-optimise-store = true;
nix.settings = {
download-buffer-size = 200000000;
auto-optimise-store = true;
substituters = ["https://hyprland.cachix.org"];
trusted-public-keys = ["hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc="];
};
nix.gc = {
automatic = true;
dates = "weekly";
@@ -94,7 +95,8 @@
};
# Set your time zone.
time.timeZone = "America/Los_Angeles";
# time.timeZone = "America/Los_Angeles";
services.automatic-timezoned.enable = true;
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
@@ -139,7 +141,8 @@
#Enable Ollama daemon
services.ollama = {
enable = true;
package = pkgs-unstable.ollama;
acceleration = "cuda";
package = pkgs-unstable.ollama-cuda;
};
#Enable Hyprland
@@ -190,17 +193,10 @@
users.users.rogueking = {
isNormalUser = true;
description = "rogueking";
extraGroups = [
"networkmanager"
"wheel"
"docker"
];
extraGroups = [ "networkmanager" "wheel" "docker" ];
shell = pkgs.zsh;
#packages = [ inputs.home-manager.packages.${pkgs.system}.default ];
packages = with pkgs; [
#apps
#cli
# thunderbird
];
};
@@ -264,8 +260,6 @@
enable = true;
allowedTCPPorts = [
22
# 80
# 443
];
logRefusedConnections = true;
};
@@ -274,7 +268,7 @@
# $ nix search wget
environment.systemPackages = with pkgs; [
# System-level only
fprintd # fingerprint daemon integration
fprintd
];
fonts.packages = with pkgs; [
@@ -300,39 +294,6 @@
backupFileExtension = "backup";
};
#home-manager = {
# extraSpecialArgs = { inherit inputs; };
# users = {
# "rogueking" = import ../home-manager/home.nix;
# };
# backupFileExtension = "backup";
#};
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
# programs.mtr.enable = true;
# programs.gnupg.agent = {
# enable = true;
# enableSSHSupport = true;
# };
# List services that you want to enable:
# Enable the OpenSSH daemon.
# services.openssh.enable = true;
# Open ports in the firewall.
# networking.firewall.allowedTCPPorts = [ ... ];
# networking.firewall.allowedUDPPorts = [ ... ];
# Or disable the firewall altogether.
# networking.firewall.enable = false;
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. Its perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "25.05"; # Did you read the comment?
system.stateVersion = "25.11"; # Did you read the comment?
}

View File

@@ -1,7 +1,3 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running nixos-help).
{
config,
pkgs,
@@ -32,17 +28,10 @@
};
networking.hostName = "eva-02"; # Define your hostname.
# Configure network proxy if necessary
# networking.proxy.default = "http://user:password@proxy:port/";
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
# Set your time zone.
time.timeZone = "America/Los_Angeles";
# Enable the X11 windowing system.
# You can disable this if you're only using the Wayland session.
# services.xserver.enable = true; # Enable for Darwin compatibility
# Enable sound with pipewire.
# hardware.pulseaudio.enable = false;
#hardware.pulseaudio = {
@@ -174,24 +163,5 @@
backupFileExtension = "backup";
};
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
# programs.mtr.enable = true;
# programs.gnupg.agent = {
# enable = true;
# enableSSHSupport = true;
# };
# List services that you want to enable:
# Enable the OpenSSH daemon.
# services.openssh.enable = true;
# Open ports in the firewall.
# networking.firewall.allowedTCPPorts = [ ... ];
# networking.firewall.allowedUDPPorts = [ ... ];
# Or disable the firewall altogether.
# networking.firewall.enable = false;
system.stateVersion = 6;
}

View File

@@ -1,7 +1,3 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running nixos-help).
{
config,
configPath,
@@ -41,7 +37,12 @@
# Nix optimizations
nix.optimise.automatic = true;
nix.settings.auto-optimise-store = true;
nix.settings = {
download-buffer-size = 200000000;
auto-optimise-store = true;
substituters = ["https://hyprland.cachix.org"];
trusted-public-keys = ["hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc="];
};
nix.gc = {
automatic = true;
dates = "weekly";
@@ -52,10 +53,6 @@
networking.hostName = "eva-03"; # Define your hostname.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
# Configure network proxy if necessary
# networking.proxy.default = "http://user:password@proxy:port/";
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
# Enable networking
networking.networkmanager.enable = true;
@@ -93,10 +90,6 @@
# Modesetting is required.
modesetting.enable = true;
# Nvidia power management. Experimental, and can cause sleep/suspend to fail.
# Enable this if you have graphical corruption issues or application crashes after waking
# up from sleep. This fixes it by saving the entire VRAM memory to /tmp/ instead
# of just the bare essentials.
# powerManagement.enable = false;
open = false;
@@ -169,9 +162,22 @@
openFirewall = true;
acceleration = "cuda";
package = pkgs-unstable.ollama-cuda;
environmentVariables = {
CUDA_VISIBLE_DEVICES = "0,1";
};
# environmentVariables = {
# CUDA_VISIBLE_DEVICES = "0,1";
# };
};
services.comfyui = {
enable = true;
enableManager = true;
listenAddress = "0.0.0.0";
dataDir = "/home/rogueking/Documents/models/comfyui-data";
user = "rogueking";
group = "users";
package = pkgs-unstable.comfy-ui-cuda;
#createUser = true; # Use existing user
# If dataDir is on a separate mount (NFS, ZFS dataset, etc.):
# requiresMounts = [ "home-myuser-comfyui\\x2ddata.mount" ];
};
services.fprintd.enable = true;
@@ -303,7 +309,8 @@
pkgs-unstable
hostname
hostTypes
;
;
host = hostname;
};
users = {
"rogueking" = import ./../../home-manager/home.nix;
@@ -311,6 +318,6 @@
backupFileExtension = "backup";
};
system.stateVersion = "25.05";
system.stateVersion = "25.11";
}