Kieran's opinionated (and probably slightly dumb) nix config
at hyprnix 14 kB view raw
1{ 2 outputs, 3 config, 4 lib, 5 pkgs, 6 inputs, 7 ... 8}: let 9 commonDeps = with pkgs; [coreutils gnugrep systemd]; 10 # Function to simplify making waybar outputs 11 mkScript = { 12 name ? "script", 13 deps ? [], 14 script ? "", 15 }: 16 lib.getExe (pkgs.writeShellApplication { 17 inherit name; 18 text = script; 19 runtimeInputs = commonDeps ++ deps; 20 }); 21 # Specialized for JSON outputs 22 mkScriptJson = { 23 name ? "script", 24 deps ? [], 25 pre ? "", 26 text ? "", 27 tooltip ? "", 28 alt ? "", 29 class ? "", 30 percentage ? "", 31 }: 32 mkScript { 33 inherit name; 34 deps = [pkgs.jq] ++ deps; 35 script = '' 36 ${pre} 37 jq -cn \ 38 --arg text "${text}" \ 39 --arg tooltip "${tooltip}" \ 40 --arg alt "${alt}" \ 41 --arg class "${class}" \ 42 --arg percentage "${percentage}" \ 43 '{text:$text,tooltip:$tooltip,alt:$alt,class:$class,percentage:$percentage}' 44 ''; 45 }; 46 47 hyprlandCfg = config.wayland.windowManager.hyprland; 48in { 49 # Let it try to start a few more times 50 systemd.user.services.waybar = { 51 Unit.StartLimitBurst = 30; 52 }; 53 programs.waybar = { 54 enable = true; 55 package = pkgs.waybar.overrideAttrs (oa: { 56 mesonFlags = (oa.mesonFlags or []) ++ ["-Dexperimental=true"]; 57 }); 58 systemd.enable = true; 59 settings = { 60 primary = { 61 exclusive = false; 62 passthrough = false; 63 height = 46; 64 margin = "6"; 65 position = "top"; 66 modules-left = 67 [ 68 "custom/menu" 69 "custom/currentplayer" 70 "custom/player" 71 "hyprland/workspaces" 72 "hyprland/submap" 73 ]; 74 75 modules-center = [ 76 "cpu" 77 "memory" 78 "disk" 79 "clock" 80 "pulseaudio" 81 "battery" 82 "idle_inhibitor" 83 # "custom/unread-mail" 84 ]; 85 86 modules-right = [ 87 # "custom/gammastep" TODO: currently broken for some reason 88 "network" 89 "bluetooth" 90 "tray" 91 "privacy" 92 "custom/webcam" 93 "custom/hostname" 94 ]; 95 96 clock = { 97 interval = 1; 98 format = "{:%d/%m %H:%M:%S}"; 99 format-alt = "{:%Y-%m-%d %H:%M:%S %z}"; 100 on-click-left = "mode"; 101 tooltip-format = '' 102 <big>{:%Y %B}</big> 103 <tt><small>{calendar}</small></tt>''; 104 }; 105 106 cpu = { 107 format = " {usage}%"; 108 }; 109 110 memory = { 111 format = " {}%"; 112 interval = 5; 113 }; 114 115 disk = { 116 interval = 5; 117 unit = "GB"; 118 format = "󰋊 {percentage_used}%"; 119 }; 120 121 pulseaudio = { 122 format = "{icon} {volume}%"; 123 format-muted = " 0%"; 124 format-icons = { 125 headphone = "󰋋 "; 126 headset = "󰋎 "; 127 portable = " "; 128 default = [ 129 " " 130 " " 131 " " 132 ]; 133 }; 134 on-click = lib.getExe pkgs.pavucontrol; 135 }; 136 137 idle_inhibitor = { 138 format = "{icon}"; 139 format-icons = { 140 activated = "󰅶 "; 141 deactivated = "󰾫 "; 142 }; 143 tooltip-format-activated = 144 "Caffinated! device will not sleep."; 145 tooltip-format-deactivated = 146 "no caffeine :( device will sleep when not in use."; 147 }; 148 149 battery = { 150 interval = 5; 151 bat = "BAT1"; 152 # full-at = 94; 153 format = "{icon} {capacity}%"; 154 format-icons = [ "󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹" ]; 155 states = { 156 battery-10 = 10; 157 battery-20 = 20; 158 battery-30 = 30; 159 battery-40 = 40; 160 battery-50 = 50; 161 battery-60 = 60; 162 battery-70 = 70; 163 battery-80 = 80; 164 battery-90 = 90; 165 battery-100 = 100; 166 }; 167 # <https://github.com/Alexays/Waybar/issues/1938> 168 # the wiki lies about this, does not match 169 # /sys/class/power_supply/BAT0/status 170 format-plugged = "󰚥 AC"; 171 format-charging-battery-10 = "󰢜 {capacity}%"; 172 format-charging-battery-20 = "󰂆 {capacity}%"; 173 format-charging-battery-30 = "󰂇 {capacity}%"; 174 format-charging-battery-40 = "󰂈 {capacity}%"; 175 format-charging-battery-50 = "󰢝 {capacity}%"; 176 format-charging-battery-60 = "󰂉 {capacity}%"; 177 format-charging-battery-70 = "󰢞 {capacity}%"; 178 format-charging-battery-80 = "󰂊 {capacity}%"; 179 format-charging-battery-90 = "󰂋 {capacity}%"; 180 format-charging-battery-100 = "󰂅 {capacity}%"; 181 }; 182 183 "hyprland/workspaces" = { 184 format = "{icon} {windows}"; 185 window-rewrite-default = " "; 186 window-rewrite-seperator = ""; 187 window-rewrite = { 188 "title<.*github.*>" = "󰊤"; 189 "title<.*youtube.*>" = ""; 190 "title<*Gmail*>" = "󰊫"; 191 "class<firefox>" = ""; 192 "class<com.obsproject.Studio>" = ""; 193 "title<OBS*>" = ""; 194 "alacritty" = ""; 195 "code" = "󰨞"; 196 "slack" = "󰒱"; 197 "initialtitle<Spotify*>" = "󰓇"; 198 "class<org.gnome.Nautilus>" = "󰉓"; 199 "class<kicad>" = ""; 200 }; 201 }; 202 203 network = { 204 interval = 3; 205 format-wifi = "{icon} {essid}"; 206 format-icons = [ "󰤟" "󰤢" "󰤥" "󰤨" ]; 207 format-ethernet = "󰈁 Connected"; 208 format-disconnected = "󱐤 "; 209 tooltip-format = '' 210 {ifname} 211 {ipaddr}/{cidr} 212 Up: {bandwidthUpBits} 213 Down: {bandwidthDownBits}''; 214 on-click = mkScript { 215 deps = [pkgs.wpa_supplicant pkgs.notify-desktop]; 216 script = ''wpa_cli reconnect; notify-desktop "reconnecting to wifi" -t 1200''; 217 }; 218 }; 219 220 bluetooth = { 221 format-on = "󰂯"; 222 format-off = "󰂲"; 223 format-disabled = "󰂲"; 224 format-connected = "󰂱 {num_connections}"; 225 format-connected-battery = "󰂱 {device_alias} ({device_battery_percentage}%) ({num_connections})"; 226 on-click = "overskride"; 227 }; 228 229 "custom/menu" = { 230 interval = 1; 231 return-type = "json"; 232 exec = mkScriptJson { 233 deps = lib.optional hyprlandCfg.enable hyprlandCfg.package; 234 text = " "; 235 tooltip = ''$(grep PRETTY_NAME /etc/os-release | cut -d '"' -f2)''; 236 class = let 237 isFullScreen = 238 if hyprlandCfg.enable 239 then "hyprctl activewindow -j | jq -e '.fullscreen' &>/dev/null" 240 else "false"; 241 in "$(if ${isFullScreen}; then echo fullscreen; fi)"; 242 }; 243 }; 244 245 "custom/hostname" = { 246 exec = mkScript {script = ''echo "$USER@$HOSTNAME"'';}; 247 on-click = mkScript {script = "systemctl --user restart waybar";}; 248 }; 249 250 "custom/unread-mail" = { 251 interval = 5; 252 return-type = "json"; 253 exec = mkScriptJson { 254 deps = [pkgs.findutils pkgs.procps]; 255 pre = '' 256 count=$(find ~/Mail/*/Inbox/new -type f | wc -l) 257 if pgrep mbsync &>/dev/null; then 258 status="syncing" 259 else 260 if [ "$count" == "0" ]; then 261 status="read" 262 else 263 status="unread" 264 fi 265 fi 266 ''; 267 text = "$count"; 268 alt = "$status"; 269 }; 270 format = "{icon} ({})"; 271 format-icons = { 272 "read" = "󰇯"; 273 "unread" = "󰇮"; 274 "syncing" = "󰁪"; 275 }; 276 }; 277 278 "custom/currentplayer" = { 279 interval = 2; 280 return-type = "json"; 281 exec = mkScriptJson { 282 deps = [pkgs.playerctl]; 283 pre = '' 284 player="$(playerctl status -f "{{playerName}}" 2>/dev/null || echo "No player active" | cut -d '.' -f1)" 285 count="$(playerctl -l 2>/dev/null | wc -l)" 286 if ((count > 1)); then 287 more=" +$((count - 1))" 288 else 289 more="" 290 fi 291 ''; 292 alt = "$player"; 293 tooltip = "$player ($count available)"; 294 text = "$more"; 295 }; 296 format = "{icon}{}"; 297 format-icons = { 298 "No player active" = " "; 299 "Celluloid" = "󰎁 "; 300 "spotify" = "󰓇 "; 301 "ncspot" = "󰓇 "; 302 "qutebrowser" = "󰖟 "; 303 "firefox" = " "; 304 "discord" = " 󰙯 "; 305 "sublimemusic" = " "; 306 "kdeconnect" = "󰄡 "; 307 "chromium" = " "; 308 }; 309 }; 310 311 privacy = { 312 "icon-spacing" = 0; 313 "icon-size" = 18; 314 "transition-duration" = 250; 315 modules = [ 316 { 317 type = "screenshare"; 318 tooltip = true; 319 "tooltip-icon-size" = 24; 320 } 321 { 322 type = "audio-out"; 323 tooltip = true; 324 "tooltip-icon-size" = 24; 325 } 326 { 327 type = "audio-in"; 328 tooltip = true; 329 "tooltip-icon-size" = 24; 330 } 331 ]; 332 }; 333 334 "custom/webcam" = { 335 return-type = "json"; 336 interval = 2; 337 exec = mkScript { 338 deps = [pkgs.jq pkgs.psmisc]; 339 script = '' 340 # get programs using the video0 endpoint 341 ps -eo user,pid,cmd -q "$(fuser /dev/video0 2>/dev/null | xargs)" |\ 342 # omit the column headings and the first line which is wireplumber 343 sed -n "1,2!p" |\ 344 # just get the pid and program columns 345 awk '{print $2 " " $3}' |\ 346 # filter out the program path 347 awk -F "/" '{print "{\"tooltip\": \"" $1 " " $NF "\"}"}' |\ 348 jq -s 'if length > 0 then {text: "󰄀 ", tooltip: (map(.tooltip) | join("\r"))} else {text: "", tooltip: ""} end' |\ 349 jq --unbuffered --compact-output 350 ''; 351 }; 352 }; 353 354 "custom/player" = { 355 exec-if = mkScript { 356 deps = [pkgs.playerctl]; 357 script = "playerctl status 2>/dev/null"; 358 }; 359 exec = let 360 format = ''{"text": "{{title}} - {{artist}}", "alt": "{{status}}", "tooltip": "{{title}} - {{artist}} ({{album}})"}''; 361 in 362 mkScript { 363 deps = [pkgs.playerctl]; 364 script = "playerctl metadata --format '${format}' 2>/dev/null"; 365 }; 366 return-type = "json"; 367 interval = 2; 368 max-length = 30; 369 format = "{icon} {}"; 370 format-icons = { 371 "Playing" = "󰐊"; 372 "Paused" = "󰏤 "; 373 "Stopped" = "󰓛"; 374 }; 375 on-click = mkScript { 376 deps = [pkgs.playerctl]; 377 script = "playerctl play-pause"; 378 }; 379 }; 380 }; 381 }; 382 # Cheatsheet: 383 # x -> all sides 384 # x y -> vertical, horizontal 385 # x y z -> top, horizontal, bottom 386 # w x y z -> top, right, bottom, left 387 style = let 388 inherit (inputs.nix-colors.lib.conversions) hexToRGBString; 389 inherit (config.colorscheme) colors; 390 toRGBA = color: opacity: "rgba(${hexToRGBString "," (lib.removePrefix "#" color)},${opacity})"; 391 in 392 /* 393 css 394 */ 395 '' 396 * { 397 font-family: Fira Sans, FiraCode Nerd Font; 398 font-size: 12pt; 399 padding: 0; 400 margin: 0 0.4em; 401 } 402 403 window#waybar { 404 padding: 0; 405 border-radius: 0.5em; 406 background-color: shade(@surface0, 0.7); 407 color: @surface2 408 } 409 .modules-left { 410 margin-left: -0.65em; 411 } 412 .modules-right { 413 margin-right: -0.65em; 414 } 415 416 #workspaces button { 417 background-color: @surface0; 418 color: @surface2; 419 padding-left: 0.2em; 420 padding-right: 0.2em; 421 margin-left: 0.25em; 422 margin-right: 0.25em; 423 margin-top: 0.4em; 424 margin-bottom: 0.4em; 425 } 426 #workspaces button.hidden { 427 background-color: @surface0; 428 color: @surface2; 429 } 430 #workspaces button.focused, 431 #workspaces button.active { 432 background-color: shade(@blue, 0.7); 433 color: @green; 434 } 435 436 #workspaces button:hover { 437 background-color: @surface3; 438 color: @surface1; 439 } 440 441 #privacy-item { 442 margin-left: 0.1em; 443 margin-right: 0.1em; 444 } 445 446 #clock { 447 padding-right: 1em; 448 padding-left: 1em; 449 border-radius: 0.5em; 450 } 451 452 #custom-menu { 453 background-color: @surface3; 454 color: @blue; 455 padding-right: 1.5em; 456 padding-left: 1em; 457 margin-right: 0; 458 border-radius: 0.5em; 459 } 460 #custom-menu.fullscreen { 461 background-color: @blue; 462 color: @green; 463 } 464 #custom-hostname { 465 background-color: @surface3; 466 color: @blue; 467 padding-right: 1em; 468 padding-left: 1em; 469 margin-left: 0; 470 border-radius: 0.5em; 471 } 472 #custom-currentplayer { 473 padding-right: 0; 474 } 475 #custom-gpu, #cpu, #memory { 476 margin-left: 0.05em; 477 margin-right: 0.55em; 478 } 479 ''; 480 }; 481}