dot
vizie's NixOS desktop: Hyprland, CachyOS kernel and gaming tweaks
| 1 | # The Hyprland desktop: compositor, lock screen, idle and wallpaper daemons, the |
| 2 | # bar and notifications. Hyprland and hyprlock themselves come from the NixOS |
| 3 | # modules (modules/nixos/desktop.nix); these write their configs. |
| 4 | # |
| 5 | # The services (hypridle, hyprpaper, waybar, dunst) run with |
| 6 | # graphical-session.target, which uwsm starts; services.nix puts them in uwsm's |
| 7 | # session slice. |
| 8 | { |
| 9 | config, |
| 10 | lib, |
| 11 | pkgs, |
| 12 | ... |
| 13 | }: |
| 14 | let |
| 15 | inherit (config.lib.dot) link; |
| 16 | |
| 17 | lua = lib.generators.mkLuaInline; |
| 18 | toLua = lib.generators.toLua { }; |
| 19 | |
| 20 | configHome = config.xdg.configHome; |
| 21 | wallpaper = ../../config/hypr/wallpaper.jpg; |
| 22 | |
| 23 | # --- Hyprland ----------------------------------------------------------------- |
| 24 | |
| 25 | mod = "SUPER"; |
| 26 | bind = keys: action: { |
| 27 | _args = [ |
| 28 | " + " |
| 29 | (lua action) |
| 30 | ]; |
| 31 | }; |
| 32 | exec = command: "hl.dsp.exec_cmd()"; |
| 33 | screenshotKeys = "/keys/scrnis"; |
| 34 | |
| 35 | # hl.dsp.dpms for hypridle, which runs it through hyprctl. |
| 36 | dpms = action: "hyprctl dispatch 'hl.dsp.dpms({ action = \"\" })'"; |
| 37 | |
| 38 | # --- Waybar ------------------------------------------------------------------- |
| 39 | |
| 40 | toggleFlyout = "/bin/bash /waybar/eww/scripts/toggle.sh"; |
| 41 | trayIcons = ../../config/waybar/icons; |
| 42 | in |
| 43 | { |
| 44 | wayland.windowManager.hyprland = { |
| 45 | enable = true; |
| 46 | # From the NixOS module (programs.hyprland), which uwsm starts. uwsm also |
| 47 | # brings up graphical-session.target, so Home Manager's own session target |
| 48 | # isn't needed. |
| 49 | package = null; |
| 50 | portalPackage = null; |
| 51 | systemd.enable = false; |
| 52 | configType = "lua"; |
| 53 | |
| 54 | settings = { |
| 55 | monitor = [ |
| 56 | { |
| 57 | output = "HDMI-A-2"; |
| 58 | mode = "2560x1440@280"; |
| 59 | position = "0x0"; |
| 60 | scale = 1; |
| 61 | vrr = 0; |
| 62 | } |
| 63 | { |
| 64 | output = "DP-1"; |
| 65 | mode = "2560x1440@144"; |
| 66 | position = "2560x-900"; |
| 67 | scale = 1; |
| 68 | transform = 1; |
| 69 | vrr = 0; |
| 70 | } |
| 71 | ]; |
| 72 | |
| 73 | # Workspaces 1-2 on HDMI-A-2, 3-5 on DP-1. |
| 74 | workspace_rule = map (workspace: { |
| 75 | workspace = toString workspace; |
| 76 | monitor = if workspace <= 2 then "HDMI-A-2" else "DP-1"; |
| 77 | persistent = true; |
| 78 | }) (lib.range 1 5); |
| 79 | |
| 80 | window_rule = [ |
| 81 | { |
| 82 | name = "windowrule-1"; |
| 83 | match.title = "^RuneLite.*"; |
| 84 | float = true; |
| 85 | } |
| 86 | { |
| 87 | name = "windowrule-2"; |
| 88 | match = { |
| 89 | class = "net-runelite-client-RuneLite"; |
| 90 | title = "win.*"; |
| 91 | }; |
| 92 | no_focus = true; |
| 93 | } |
| 94 | { |
| 95 | name = "windowrule-3"; |
| 96 | match.class = ".*"; |
| 97 | immediate = true; |
| 98 | } |
| 99 | # Battle.net (Proton via Faugus). The class is shared by every umu game |
| 100 | # without its own ID, so match on the title too. |
| 101 | { |
| 102 | name = "battlenet-float"; |
| 103 | match = { |
| 104 | class = "^steam_app_default$"; |
| 105 | title = "^Battle\\.net.*"; |
| 106 | }; |
| 107 | float = true; |
| 108 | } |
| 109 | # xembedsniproxy (pkgs/xembedsniproxy) puts Wine/Proton tray icons such |
| 110 | # as Battle.net into Waybar's tray. It keeps a small helper window mapped |
| 111 | # for the icons it hosts, which Hyprland would otherwise show as a |
| 112 | # floating window. |
| 113 | { |
| 114 | name = "xembedsniproxy"; |
| 115 | match.class = "^xembedsniproxy$"; |
| 116 | opacity = "0.0 override 0.0 override"; |
| 117 | border_size = 0; |
| 118 | no_blur = true; |
| 119 | no_shadow = true; |
| 120 | no_anim = true; |
| 121 | no_focus = true; |
| 122 | no_initial_focus = true; |
| 123 | } |
| 124 | # Dolphin's transparency comes from Kvantum's translucent_windows (see |
| 125 | # config/kvantum/RiceDark.kvconfig), not a forced window opacity: Kvantum |
| 126 | # puts alpha in the background only, so text and icons stay opaque, as |
| 127 | # in kitty. decoration.blur blurs through it. If Dolphin looks opaque |
| 128 | # after applying the Kvantum theme, this rule can come back: |
| 129 | # { name = "windowrule-4"; match.class = "^(org.kde.dolphin)$"; opacity = "0.90 0.80"; } |
| 130 | |
| 131 | # Claude's desktop app creates its window opaque (transparent: false in |
| 132 | # the Electron bundle) and has no user stylesheet, so unlike kitty and |
| 133 | # Dolphin it can't put alpha in the background only. A forced window |
| 134 | # opacity is the closest it gets: decoration.blur frosts what shows |
| 135 | # through, at the cost of the text being translucent too. |
| 136 | { |
| 137 | name = "claude-frosted"; |
| 138 | match.class = "^com\\.anthropic\\.Claude$"; |
| 139 | opacity = "0.80 0.80"; |
| 140 | } |
| 141 | ]; |
| 142 | |
| 143 | # Blur behind the translucent layers: Waybar, fuzzel ("launcher"), dunst |
| 144 | # and the control centre. |
| 145 | layer_rule = [ |
| 146 | { |
| 147 | name = "layerrule-1"; |
| 148 | match.namespace = "waybar"; |
| 149 | blur = true; |
| 150 | ignore_alpha = 0; |
| 151 | blur_popups = true; |
| 152 | } |
| 153 | { |
| 154 | name = "layerrule-2"; |
| 155 | match.namespace = "launcher"; |
| 156 | ignore_alpha = 0; |
| 157 | blur = true; |
| 158 | } |
| 159 | { |
| 160 | name = "layerrule-3"; |
| 161 | match.namespace = "notifications"; |
| 162 | ignore_alpha = 0; |
| 163 | blur = true; |
| 164 | } |
| 165 | { |
| 166 | name = "layerrule-4"; |
| 167 | match.namespace = "eww-control-center"; |
| 168 | ignore_alpha = 0; |
| 169 | blur = true; |
| 170 | } |
| 171 | ]; |
| 172 | |
| 173 | bind = [ |
| 174 | (bind "SHIFT + S" (exec "imsh-shot area -Cufa@ -s1")) |
| 175 | (bind "SHIFT + F" (exec "imsh-shot screen -Cua@")) |
| 176 | (bind "SHIFT + R" (exec "imsh-cast -Cua@")) |
| 177 | (bind "Return" (exec "/fuzzel/launch")) |
| 178 | (bind "T" (exec (lib.getExe config.programs.kitty.package))) |
| 179 | (bind "W" (exec "firefox")) |
| 180 | (bind "Q" "hl.dsp.window.close()") |
| 181 | (bind "F" ''hl.dsp.window.float({ action = "toggle" })'') |
| 182 | (bind "G" ''hl.dsp.window.fullscreen({ mode = "fullscreen", action = "toggle" })'') |
| 183 | (bind "mouse_down" ''hl.dsp.focus({ workspace = "m+1" })'') |
| 184 | (bind "mouse_up" ''hl.dsp.focus({ workspace = "m-1" })'') |
| 185 | (bind "mouse:272" "hl.dsp.window.drag()") |
| 186 | (bind "mouse:273" "hl.dsp.window.resize()") |
| 187 | (bind "Delete" "hl.dsp.exit()") |
| 188 | ]; |
| 189 | |
| 190 | env = |
| 191 | lib.mapAttrsToList |
| 192 | (name: value: { |
| 193 | _args = [ |
| 194 | name |
| 195 | value |
| 196 | ]; |
| 197 | }) |
| 198 | { |
| 199 | LIBVA_DRIVER_NAME = "nvidia"; |
| 200 | # libva-nvidia-driver's backend, for Firefox's hardware video decoding. |
| 201 | NVD_BACKEND = "direct"; |
| 202 | # Lets Firefox's video decoder process reach the NVIDIA driver. |
| 203 | MOZ_DISABLE_RDD_SANDBOX = "1"; |
| 204 | XDG_SESSION_TYPE = "wayland"; |
| 205 | GBM_BACKEND = "nvidia-drm"; |
| 206 | __GLX_VENDOR_LIBRARY_NAME = "nvidia"; |
| 207 | XCURSOR_THEME = config.home.pointerCursor.name; |
| 208 | XCURSOR_SIZE = toString config.home.pointerCursor.size; |
| 209 | QT_QPA_PLATFORMTHEME = config.qt.platformTheme.name; |
| 210 | }; |
| 211 | |
| 212 | config = { |
| 213 | general = { |
| 214 | border_size = 1; |
| 215 | gaps_in = 5; |
| 216 | gaps_out = 17; |
| 217 | layout = "dwindle"; |
| 218 | col = { |
| 219 | active_border = "rgba(00000000)"; |
| 220 | inactive_border = "rgba(00000000)"; |
| 221 | }; |
| 222 | allow_tearing = true; |
| 223 | }; |
| 224 | decoration = { |
| 225 | rounding = 10; |
| 226 | # No visible borders; the focused window is the one that isn't dimmed. |
| 227 | dim_inactive = true; |
| 228 | dim_strength = 0.15; |
| 229 | blur = { |
| 230 | enabled = true; |
| 231 | size = 9; |
| 232 | passes = 3; |
| 233 | new_optimizations = true; |
| 234 | # Blur tooltips and context menus (Kvantum's popup_blurring is on for this). |
| 235 | popups = true; |
| 236 | }; |
| 237 | shadow = { |
| 238 | enabled = false; |
| 239 | render_power = 3; |
| 240 | range = 4; |
| 241 | color = "rgba(1a1a1aee)"; |
| 242 | }; |
| 243 | }; |
| 244 | input = { |
| 245 | kb_layout = "no"; |
| 246 | follow_mouse = 1; |
| 247 | sensitivity = 0; |
| 248 | accel_profile = "flat"; |
| 249 | }; |
| 250 | misc = { |
| 251 | disable_hyprland_logo = true; |
| 252 | disable_splash_rendering = true; |
| 253 | # The wallpaper's average colour instead of black, so the moment |
| 254 | # between the login screen and hyprpaper drawing the wallpaper doesn't |
| 255 | # flash black. |
| 256 | background_color = "rgb(121d1a)"; |
| 257 | }; |
| 258 | ecosystem.no_donation_nag = true; |
| 259 | binds.scroll_event_delay = 10; |
| 260 | # Software cursor, for tearing. Hyprland (0.56) refuses to tear while a |
| 261 | # hardware cursor is visible, because the kernel can't update the cursor |
| 262 | # plane during a tearing flip. So with a hardware cursor a game only |
| 263 | # tears while it hides the pointer (e.g. mouselook) and is vsynced |
| 264 | # otherwise, and switching back and forth left WoW's cursor shape stale. |
| 265 | # With a software cursor the game tears all the time; the price is that |
| 266 | # frames are composited (one extra copy) while the pointer is visible. |
| 267 | cursor.no_hardware_cursors = true; |
| 268 | # Fullscreen games (e.g. WoW) scan out straight to the monitor instead |
| 269 | # of being copied by the compositor. With the software cursor above this |
| 270 | # only applies while the game hides the pointer. 1 = always; XWayland |
| 271 | # games don't set a content type, so 2 (auto) won't trigger. |
| 272 | render.direct_scanout = 1; |
| 273 | }; |
| 274 | }; |
| 275 | |
| 276 | # What happens at login (the apps' placement rules), in Lua: linked to the |
| 277 | # checkout like the other hand-edited configs. |
| 278 | extraLuaFiles.startup = link "hypr/startup.lua"; |
| 279 | }; |
| 280 | |
| 281 | # --- Lock screen, idle, wallpaper ----------------------------------------------- |
| 282 | |
| 283 | # Styled to match the greetd login screen (config/greetd/style.css): same |
| 284 | # bamboo wallpaper, translucent black boxes, #eee text, rounding 10. |
| 285 | programs.hyprlock = { |
| 286 | enable = true; |
| 287 | # From the NixOS module (programs.hyprlock), which also sets up its PAM. |
| 288 | package = null; |
| 289 | settings = |
| 290 | let |
| 291 | centered = { |
| 292 | monitor = "HDMI-A-2"; |
| 293 | halign = "center"; |
| 294 | valign = "center"; |
| 295 | }; |
| 296 | in |
| 297 | { |
| 298 | "$font" = "NotoSans Nerd Font"; |
| 299 | "$text" = "rgb(238, 238, 238)"; |
| 300 | "$box" = "rgba(0, 0, 0, 0.2)"; |
| 301 | |
| 302 | general = { |
| 303 | hide_cursor = false; |
| 304 | ignore_empty_input = true; |
| 305 | }; |
| 306 | |
| 307 | animations = { |
| 308 | enabled = true; |
| 309 | bezier = "ease, 0.25, 0.1, 0.25, 1"; |
| 310 | animation = [ |
| 311 | "fadeIn, 1, 4, ease" |
| 312 | "fadeOut, 1, 4, ease" |
| 313 | "inputFieldDots, 1, 2, ease" |
| 314 | ]; |
| 315 | }; |
| 316 | |
| 317 | # Light blur and dim so the text stays readable over the leaves, like |
| 318 | # the blurred panel behind the login box. |
| 319 | background = { |
| 320 | monitor = ""; |
| 321 | path = ""; |
| 322 | blur_passes = 2; |
| 323 | blur_size = 5; |
| 324 | brightness = 0.75; |
| 325 | }; |
| 326 | |
| 327 | label = [ |
| 328 | # Clock |
| 329 | ( |
| 330 | centered |
| 331 | // { |
| 332 | text = ''<span font_weight="light">$TIME</span>''; |
| 333 | color = "$text"; |
| 334 | font_family = "$font"; |
| 335 | font_size = 110; |
| 336 | position = "0, 170"; |
| 337 | } |
| 338 | ) |
| 339 | # Date |
| 340 | ( |
| 341 | centered |
| 342 | // { |
| 343 | text = ''cmd[update:60000] date +"%A %-d %B"''; |
| 344 | color = "rgba(238, 238, 238, 0.75)"; |
| 345 | font_family = "$font"; |
| 346 | font_size = 18; |
| 347 | position = "0, 70"; |
| 348 | } |
| 349 | ) |
| 350 | # User |
| 351 | ( |
| 352 | centered |
| 353 | // { |
| 354 | text = "$USER"; |
| 355 | color = "$text"; |
| 356 | font_family = "$font"; |
| 357 | font_size = 15; |
| 358 | position = "0, 0"; |
| 359 | } |
| 360 | ) |
| 361 | ]; |
| 362 | |
| 363 | input-field = centered // { |
| 364 | size = "320, 50"; |
| 365 | rounding = 10; |
| 366 | outline_thickness = 0; |
| 367 | inner_color = "$box"; |
| 368 | outer_color = "rgba(0, 0, 0, 0)"; |
| 369 | check_color = "rgba(170, 255, 255, 0.5)"; |
| 370 | fail_color = "rgba(255, 107, 107, 0.6)"; |
| 371 | capslock_color = "rgba(244, 214, 159, 0.5)"; |
| 372 | font_color = "$text"; |
| 373 | font_family = "$font"; |
| 374 | fade_on_empty = false; |
| 375 | placeholder_text = ''<span foreground="##eeeeeeaa">Password</span>''; |
| 376 | fail_text = ''<span foreground="##eeeeee">$FAIL ($ATTEMPTS)</span>''; |
| 377 | dots_size = 0.22; |
| 378 | dots_spacing = 0.4; |
| 379 | dots_center = true; |
| 380 | position = "0, -60"; |
| 381 | }; |
| 382 | }; |
| 383 | }; |
| 384 | |
| 385 | services.hypridle = { |
| 386 | enable = true; |
| 387 | settings = { |
| 388 | general = { |
| 389 | lock_cmd = "pidof hyprlock || hyprlock"; |
| 390 | before_sleep_cmd = "loginctl lock-session"; |
| 391 | after_sleep_cmd = dpms "on"; |
| 392 | }; |
| 393 | listener = [ |
| 394 | # Lock after 10 minutes idle (respects inhibitors, e.g. fullscreen video). |
| 395 | { |
| 396 | timeout = 600; |
| 397 | on-timeout = "loginctl lock-session"; |
| 398 | } |
| 399 | { |
| 400 | timeout = 1; |
| 401 | on-resume = dpms "on"; |
| 402 | on-timeout = "true"; |
| 403 | ignore_inhibit = true; |
| 404 | } |
| 405 | { |
| 406 | timeout = 300; |
| 407 | on-timeout = dpms "off"; |
| 408 | ignore_inhibit = true; |
| 409 | } |
| 410 | ]; |
| 411 | }; |
| 412 | }; |
| 413 | |
| 414 | # A new picture from the folder every 30 seconds, on every monitor. |
| 415 | services.hyprpaper = { |
| 416 | enable = true; |
| 417 | settings = { |
| 418 | wallpaper = { |
| 419 | monitor = ""; |
| 420 | path = "/Wallpapers"; |
| 421 | timeout = 30; |
| 422 | }; |
| 423 | splash = false; |
| 424 | }; |
| 425 | }; |
| 426 | |
| 427 | # --- Waybar --------------------------------------------------------------------- |
| 428 | |
| 429 | # The flyouts it opens (control centre, notifications) are Eww windows, in |
| 430 | # config/waybar/eww; its scripts are in config/waybar/scripts. Both stay linked |
| 431 | # to the checkout (files.nix). |
| 432 | programs.waybar = { |
| 433 | enable = true; |
| 434 | systemd.enable = true; |
| 435 | |
| 436 | settings.main = { |
| 437 | layer = "top"; |
| 438 | position = "top"; |
| 439 | height = 36; |
| 440 | margin-left = 15; |
| 441 | margin-right = 15; |
| 442 | margin-top = 15; |
| 443 | |
| 444 | modules-left = [ |
| 445 | "hyprland/workspaces" |
| 446 | "hyprland/window" |
| 447 | ]; |
| 448 | modules-center = [ |
| 449 | "custom/imsh-cast-monitor" |
| 450 | "clock" |
| 451 | ]; |
| 452 | modules-right = [ |
| 453 | "tray" |
| 454 | "custom/notifications" |
| 455 | "custom/notifications-count" |
| 456 | "custom/control-center" |
| 457 | ]; |
| 458 | |
| 459 | "hyprland/workspaces" = { |
| 460 | format = "{name}"; |
| 461 | on-click = "activate"; |
| 462 | sort-by = "number"; |
| 463 | }; |
| 464 | "hyprland/window".max-length = 60; |
| 465 | |
| 466 | "custom/imsh-cast-monitor" = { |
| 467 | format = "{text}"; |
| 468 | exec = "imsh-cast-monitor"; |
| 469 | return-type = "json"; |
| 470 | hide-empty-text = true; |
| 471 | }; |
| 472 | |
| 473 | clock = { |
| 474 | format = "{:%H:%M}"; |
| 475 | format-alt = "{:%A, %B %d, %Y (%R)}"; |
| 476 | tooltip-format = "<tt><big>{calendar}</big></tt>"; |
| 477 | calendar = { |
| 478 | mode = "month"; |
| 479 | iso8601 = true; |
| 480 | mode-mon-col = 3; |
| 481 | weeks-pos = "right"; |
| 482 | on-scroll = 1; |
| 483 | format = { |
| 484 | months = "<span color='#ffffff'><b>{}</b></span>"; |
| 485 | days = "<span color='#ffffff'>{}</span>"; |
| 486 | weeks = "<span color='#8a8a8a'>W{:%V}</span>"; |
| 487 | weekdays = "<span color='#b0b0b0'><b>{}</b></span>"; |
| 488 | today = "<span color='#aaffff'><b><u>{}</u></b></span>"; |
| 489 | }; |
| 490 | }; |
| 491 | actions = { |
| 492 | on-click-right = "mode"; |
| 493 | on-scroll-up = "shift_up"; |
| 494 | on-scroll-down = "shift_down"; |
| 495 | }; |
| 496 | }; |
| 497 | |
| 498 | # Own icons for the tray apps, by their StatusNotifierItem ID. |
| 499 | tray = { |
| 500 | icon-size = 16; |
| 501 | spacing = 10; |
| 502 | order = [ "discord_status_icon_1" ]; |
| 503 | icons = { |
| 504 | Signal_status_icon_1 = "/signal.svg"; |
| 505 | Bitwarden_status_icon_1 = "/bitwarden.svg"; |
| 506 | WowUpCf_status_icon_1 = "/wowup.svg"; |
| 507 | Claude_status_icon_1 = "/claude.svg"; |
| 508 | steam_app_default = "/battlenet.svg"; |
| 509 | faugus-launcher = "/faugus.svg"; |
| 510 | steam = "/steam.svg"; |
| 511 | "tray-icon tray app opendeck" = "/opendeck.svg"; |
| 512 | }; |
| 513 | }; |
| 514 | |
| 515 | "custom/notifications" = { |
| 516 | format = "<span size='12288'></span>"; |
| 517 | tooltip = false; |
| 518 | on-click = " notifications"; |
| 519 | }; |
| 520 | "custom/notifications-count" = { |
| 521 | exec = "/bin/python3 /waybar/scripts/notification-count.py"; |
| 522 | return-type = "json"; |
| 523 | hide-empty-text = true; |
| 524 | tooltip = false; |
| 525 | on-click = " notifications"; |
| 526 | }; |
| 527 | "custom/control-center" = { |
| 528 | format = " "; |
| 529 | tooltip = false; |
| 530 | on-click = toggleFlyout; |
| 531 | }; |
| 532 | }; |
| 533 | |
| 534 | style = '' |
| 535 | * { |
| 536 | border: none; |
| 537 | border-radius: 17px; |
| 538 | font-family: NotoSans Nerd Font Regular; |
| 539 | font-size: 12px; |
| 540 | font-weight: bolder; |
| 541 | background: transparent; |
| 542 | color: white; |
| 543 | min-height: 0; |
| 544 | } |
| 545 | |
| 546 | window#waybar { |
| 547 | background: rgba(0, 0, 0, 0.1); |
| 548 | } |
| 549 | |
| 550 | window#waybar #tray menu { |
| 551 | background: rgba(0, 0, 0, 0.1); |
| 552 | } |
| 553 | |
| 554 | #tray menu :hover { |
| 555 | background: rgba(0, 0, 0, 0.1); |
| 556 | } |
| 557 | |
| 558 | #tray menu :hover label { |
| 559 | background: rgba(0, 0, 0, 0.1); |
| 560 | } |
| 561 | |
| 562 | tooltip { |
| 563 | background: rgba(0, 0, 0, 0.1); |
| 564 | } |
| 565 | |
| 566 | /* Workspace buttons are pills inset 4px from the bar's edge, so their 13px radius |
| 567 | is concentric with the bar's 17px one. Everything GTK's theme draws on a button |
| 568 | (border, shadows, hover gradient) is reset here, as it otherwise shows up as a |
| 569 | square box on hover. */ |
| 570 | #workspaces button { |
| 571 | margin: 4px 0 4px 4px; |
| 572 | padding: 0 6px; |
| 573 | min-width: 16px; |
| 574 | border: none; |
| 575 | border-radius: 13px; |
| 576 | outline: none; |
| 577 | box-shadow: none; |
| 578 | text-shadow: none; |
| 579 | background-color: transparent; |
| 580 | /* Active-workspace underline: 2px line that grows from the centre (see .active). */ |
| 581 | background-image: linear-gradient(white, white); |
| 582 | background-repeat: no-repeat; |
| 583 | background-position: 50% 92%; |
| 584 | background-size: 0 2px; |
| 585 | transition: background-size 200ms ease-out, background-color 150ms ease-out; |
| 586 | } |
| 587 | |
| 588 | #workspaces button:hover { |
| 589 | background-color: rgba(255, 255, 255, 0.12); |
| 590 | } |
| 591 | |
| 592 | #tray window decoration { |
| 593 | border-style: none; |
| 594 | box-shadow: none; |
| 595 | } |
| 596 | |
| 597 | #workspaces button.active { |
| 598 | background-size: 10px 2px; |
| 599 | } |
| 600 | |
| 601 | /* Workspaces with no windows are dimmed, unless it's the one you're on. The colour |
| 602 | goes on the label because the `*` rule above gives every label its own white. */ |
| 603 | #workspaces button label { |
| 604 | transition: color 150ms ease-out; |
| 605 | } |
| 606 | |
| 607 | #workspaces button.empty:not(.active) label { |
| 608 | color: rgba(255, 255, 255, 0.4); |
| 609 | } |
| 610 | |
| 611 | #clock, #tray, #custom-notifications, #custom-control-center { |
| 612 | padding: 0 7px; |
| 613 | } |
| 614 | |
| 615 | /* Notification count: a small white disc overlapping the bell's top right corner. |
| 616 | The margins shrink it from the bar's full height to 12px; the negative left margin |
| 617 | pulls it over the bell. Hidden by Waybar when there are no notifications. */ |
| 618 | #custom-notifications-count { |
| 619 | min-width: 6px; |
| 620 | padding: 0 3px; |
| 621 | margin: 7px 3px 17px -11px; |
| 622 | border-radius: 6px; |
| 623 | background: white; |
| 624 | color: #18181c; |
| 625 | font-size: 8px; |
| 626 | } |
| 627 | |
| 628 | .imsh-cast-monitor.recording { |
| 629 | color: #f00; |
| 630 | } |
| 631 | |
| 632 | .imsh-cast-monitor.error { |
| 633 | color: #ff0; |
| 634 | } |
| 635 | |
| 636 | /* Control-center button: your logo as a pre-rendered 18px image. |
| 637 | GTK stretches SVG backgrounds as pixels (which makes them blurry), so logo-18.png is |
| 638 | rendered at exactly 18px, and logo-36.png is used automatically on 2x-scaled monitors. */ |
| 639 | #custom-control-center { |
| 640 | min-width: 18px; |
| 641 | min-height: 18px; |
| 642 | background-image: -gtk-scaled(url("file://"), |
| 643 | url("file://")); |
| 644 | background-repeat: no-repeat; |
| 645 | background-position: center; |
| 646 | background-size: 18px 18px; |
| 647 | margin-right: 6px; /* gap between the logo and the end of the bar */ |
| 648 | } |
| 649 | ''; |
| 650 | }; |
| 651 | |
| 652 | # --- Notifications -------------------------------------------------------------- |
| 653 | |
| 654 | services.dunst = { |
| 655 | enable = true; |
| 656 | settings = |
| 657 | let |
| 658 | # Near-transparent, over Hyprland's blur (the "notifications" layer rule). |
| 659 | urgency = timeout: { |
| 660 | inherit timeout; |
| 661 | background = "#25252501"; |
| 662 | foreground = "#ffffff"; |
| 663 | frame_color = "#25252501"; |
| 664 | }; |
| 665 | in |
| 666 | { |
| 667 | global = { |
| 668 | monitor = 0; |
| 669 | follow = "mouse"; |
| 670 | alignment = "center"; |
| 671 | width = 360; |
| 672 | offset = 15; |
| 673 | origin = "top-right"; |
| 674 | padding = 12; |
| 675 | horizontal_padding = 12; |
| 676 | frame_width = 0; |
| 677 | markup = "full"; |
| 678 | icon_position = "off"; |
| 679 | font = "DejaVu Sans Mono 10"; |
| 680 | stack_duplicates = true; |
| 681 | hide_duplicate_count = true; |
| 682 | separator_height = 0; |
| 683 | fullscreen = "pushback"; |
| 684 | corner_radius = 15; |
| 685 | # Left click does what the notification is for (e.g. opens the chat), |
| 686 | # then closes it. Middle click only dismisses it, right click |
| 687 | # dismisses all. |
| 688 | mouse_left_click = "do_action, close_current"; |
| 689 | mouse_middle_click = "close_current"; |
| 690 | mouse_right_click = "close_all"; |
| 691 | progress_bar = true; |
| 692 | }; |
| 693 | urgency_low = urgency 4; |
| 694 | urgency_normal = urgency 8; |
| 695 | urgency_critical = urgency 16; |
| 696 | status = { |
| 697 | category = "status"; |
| 698 | set_stack_tag = "status"; |
| 699 | history_ignore = true; |
| 700 | timeout = 2; |
| 701 | }; |
| 702 | }; |
| 703 | }; |
| 704 | } |