Flake for my NixOS devices
1{inputs, ...}: {
2 config,
3 lib,
4 pkgs,
5 ...
6}: {
7 imports = [inputs.niri.homeModules.niri];
8
9 options.cow.gdi = {
10 enable = lib.mkEnableOption "Niri + Customizations";
11 doIdle = lib.mkEnableOption "Turn off screen, sleep, etc. from inactivity";
12 };
13
14 config = let
15 iconTheme = {
16 name = "Tela-green";
17 package = pkgs.tela-icon-theme;
18 };
19 cursorTheme = {
20 name = "catppuccin-mocha-dark-cursors";
21 package = pkgs.catppuccin-cursors.mochaDark;
22 size = 24;
23 };
24 in
25 lib.mkIf config.cow.gdi.enable {
26 nixpkgs.overlays = [inputs.niri.overlays.niri];
27
28 # Me when I'm stupid and enable my preferred keyring unconditionally in a flake for a WM
29 services.gnome-keyring.enable = lib.mkForce false;
30
31 home.packages = with pkgs; [
32 alsa-utils
33
34 dconf
35
36 cursorTheme.package
37 iconTheme.package
38
39 xdg-terminal-exec # For gtk-launch, etc to be able to open `Terminal` desktop entries
40 wezterm
41
42 # Shell Components
43 hyprlock
44
45 ## Waybar
46 qt6.qttools # For component
47
48 pavucontrol
49
50 wf-recorder
51 slurp
52 grim
53 xdg-utils
54 swappy
55 libnotify
56 swaynotificationcenter
57 wl-clipboard
58
59 hunspell
60 hunspellDicts.en_US-large
61 ];
62
63 xdg.mimeApps = {
64 enable = true;
65 defaultApplications = {
66 "application/pdf" = lib.mkIf config.cow.firefox.enable "firefox-devedition.desktop";
67 "image/*" = lib.mkIf config.cow.firefox.enable "firefox-devedition.desktop";
68 "text/*" = lib.mkIf config.cow.neovim.enable "neovide.desktop";
69 "inode/directory" = lib.mkIf config.cow.yazi.enable "yazi.desktop";
70 "inode/mount-point" = lib.mkIf config.cow.yazi.enable "yazi.desktop";
71 "x-scheme-handler/file" = lib.mkIf config.cow.yazi.enable "yazi.desktop";
72 };
73 };
74
75 fonts.fontconfig.enable = false;
76
77 programs.niri = {
78 enable = true;
79 settings = {
80 prefer-no-csd = true;
81
82 environment =
83 {
84 NIXOS_OZONE_WL = "1";
85 TERMINAL = lib.getExe pkgs.wezterm;
86 }
87 // (builtins.mapAttrs (_: v: builtins.toString v) config.home.sessionVariables); # TODO: Hack?
88
89 screenshot-path = "~/Pictures/Screenshots/%Y%m%d_%H%M%S.png";
90
91 xwayland-satellite.path = lib.getExe pkgs.xwayland-satellite-unstable;
92
93 window-rules = [
94 {
95 geometry-corner-radius = let
96 s = 10.0;
97 in {
98 top-left = s;
99 top-right = s;
100 bottom-left = s;
101 bottom-right = s;
102 };
103 clip-to-geometry = true;
104 }
105 ];
106
107 hotkey-overlay = {
108 hide-not-bound = true;
109 skip-at-startup = true;
110 };
111
112 clipboard.disable-primary = true;
113
114 input = {
115 focus-follows-mouse.enable = true;
116 keyboard.numlock = true;
117 # power-key-handling.enable = false;
118 touchpad.natural-scroll = true;
119 };
120
121 layout = {
122 default-column-width.proportion = 1.;
123 focus-ring.active.gradient = {
124 in' = "oklab";
125 angle = 135;
126 relative-to = "workspace-view";
127 from = "#74c7ec";
128 to = "#cba6f7";
129 };
130 };
131
132 binds = with config.lib.niri.actions; let
133 spawnPkg = p: spawn "${lib.getExe p}";
134 move-column-to-workspace = n: {move-column-to-workspace = [n];};
135 terminal = pkgs.wezterm;
136 spawnTerm = spawnPkg terminal;
137 spawnPlayerctl = spawnPkg pkgs.playerctl;
138 spawnRofi = spawn "rofi";
139 spawnSh = spawn "sh" "-c";
140 spawnOsd = spawn "${pkgs.swayosd}/bin/swayosd-client";
141 launchDesktop = x: spawn "${pkgs.gtk3}/bin/gtk-launch" "${x}.desktop";
142 brightness = spawnOsd "--brightness";
143 volume = spawnOsd "--output-volume";
144 in {
145 # Niri Stuff
146
147 ## Basics
148 "Mod+X".action = quit;
149 "Mod+Tab".action = toggle-overview;
150 "Mod+Slash".action = show-hotkey-overlay;
151 "Mod+C".action = close-window;
152
153 "Mod+Left".action = focus-column-left;
154 "Mod+Down".action = focus-window-down;
155 "Mod+Up".action = focus-window-up;
156 "Mod+Right".action = focus-column-right;
157
158 "Mod+Shift+Left".action = move-column-left;
159 "Mod+Shift+Down".action = move-window-down;
160 "Mod+Shift+Up".action = move-window-up;
161 "Mod+Shift+Right".action = move-column-right;
162
163 "Mod+Home".action = focus-column-first;
164 "Mod+End".action = focus-column-last;
165 "Mod+Shift+Home".action = move-column-to-first;
166 "Mod+Shift+End".action = move-column-to-last;
167
168 "Mod+BracketLeft".action = consume-or-expel-window-left;
169 "Mod+BracketRight".action = consume-or-expel-window-right;
170
171 "Mod+F".action = maximize-column;
172 "Mod+Shift+F".action = fullscreen-window;
173 "Mod+Ctrl+F".action = expand-column-to-available-width;
174 "Mod+Ctrl+Shift+F".action = toggle-windowed-fullscreen;
175
176 "Mod+Page_Down".action = focus-workspace-down;
177 "Mod+Page_Up".action = focus-workspace-up;
178 "Mod+U".action = focus-workspace-down;
179 "Mod+I".action = focus-workspace-up;
180 "Mod+Shift+Page_Down".action = move-column-to-workspace-down;
181 "Mod+Shift+Page_Up".action = move-column-to-workspace-up;
182 "Mod+Shift+U".action = move-column-to-workspace-down;
183 "Mod+Shift+I".action = move-column-to-workspace-up;
184
185 "Mod+1".action = focus-workspace 1;
186 "Mod+2".action = focus-workspace 2;
187 "Mod+3".action = focus-workspace 3;
188 "Mod+4".action = focus-workspace 4;
189 "Mod+5".action = focus-workspace 5;
190 "Mod+6".action = focus-workspace 6;
191 "Mod+7".action = focus-workspace 7;
192 "Mod+8".action = focus-workspace 8;
193 "Mod+9".action = focus-workspace 9;
194 "Mod+Ctrl+1".action = move-column-to-workspace 1;
195 "Mod+Ctrl+2".action = move-column-to-workspace 2;
196 "Mod+Ctrl+3".action = move-column-to-workspace 3;
197 "Mod+Ctrl+4".action = move-column-to-workspace 4;
198 "Mod+Ctrl+5".action = move-column-to-workspace 5;
199 "Mod+Ctrl+6".action = move-column-to-workspace 6;
200 "Mod+Ctrl+7".action = move-column-to-workspace 7;
201 "Mod+Ctrl+8".action = move-column-to-workspace 8;
202 "Mod+Ctrl+9".action = move-column-to-workspace 9;
203
204 "Mod+Escape" = {
205 action = toggle-keyboard-shortcuts-inhibit;
206 allow-inhibiting = false;
207 };
208
209 "Mod+W".action = spawn "systemctl" "--user" "restart" "waybar.service";
210 "Mod+Shift+W".action = spawn "systemctl" "--user" "stop" "waybar.service";
211
212 "Mod+Shift+R".action =
213 spawnSh "pkill wf-recorder --signal SIGINT ||${pkgs.nushell}/bin/nu ${../res/screenrec.nu}";
214 "Mod+Shift+S".action = {
215 screenshot = [];
216 };
217 "Print".action = {
218 screenshot = [];
219 };
220 "Mod+L".action = spawnSh "pidof hyprlock || hyprlock --immediate";
221 "Mod+Z".action = spawn "systemctl" "suspend";
222 "Super+Alt+Ctrl+Shift+L".action = spawn "xdg-open" "https://linkedin.com";
223
224 # Terminal
225 "Mod+T".action = spawnTerm;
226
227 # Rofi
228 "Mod+S".action = spawnRofi "-show" "drun" "-show-icons";
229 "Mod+B".action = spawnPkg pkgs.rofi-bluetooth;
230 "Mod+Shift+E".action = spawnRofi "-modi" "emoji" "-show" "emoji";
231 "Mod+Alt+C".action =
232 spawnRofi "-show" "calc" "-modi" "calc" "-no-show-match" "-no-sort" "-calc-command"
233 "echo -n '{result}' | wl-copy";
234 "Mod+V".action =
235 spawnSh "cliphist list | sed -r 's/\[\[ binary data (.* .iB) (.*) (.*) \]\]/ \2 Image (\3, \1)/g' | rofi -dmenu -display-columns 2 -p Clipboard | cliphist decode | wl-copy";
236 "Mod+Alt+V".action =
237 spawnSh "echo -e \"Yes\\nNo\" | [[ $(rofi -dmenu -mesg \"Clear Clipboard History?\" -p Clear) == \"Yes\" ]] && cliphist wipe";
238
239 # Yazi
240 "Mod+E".action = lib.mkIf config.cow.yazi.enable (launchDesktop "yazi");
241
242 # Firefox
243 "Mod+Q".action = lib.mkIf config.cow.firefox.enable (launchDesktop "firefox-devedition");
244
245 # Pavucontrol
246 "Mod+A".action = spawnPkg pkgs.pavucontrol "--tab" "5";
247
248 # Brightness
249 "XF86MonBrightnessUp" = {
250 action = brightness "raise";
251 allow-when-locked = true;
252 };
253 "XF86MonBrightnessDown" = {
254 action = brightness "lower";
255 allow-when-locked = true;
256 };
257
258 # Volume
259 "XF86AudioRaiseVolume" = {
260 action = volume "raise";
261 allow-when-locked = true;
262 };
263 "XF86AudioLowerVolume" = {
264 action = volume "lower";
265 allow-when-locked = true;
266 };
267 "XF86AudioMute" = {
268 action = volume "mute-toggle";
269 allow-when-locked = true;
270 };
271
272 # Playerctl
273 "XF86AudioPlay" = {
274 action = spawnPlayerctl "play-pause";
275 allow-when-locked = true;
276 };
277 "XF86AudioPause" = {
278 action = spawnPlayerctl "pause";
279 allow-when-locked = true;
280 };
281 "XF86AudioStop" = {
282 action = spawnPlayerctl "stop";
283 allow-when-locked = true;
284 };
285 "XF86AudioNext" = {
286 action = spawnPlayerctl "next";
287 allow-when-locked = true;
288 };
289 "XF86AudioPrev" = {
290 action = spawnPlayerctl "previous";
291 allow-when-locked = true;
292 };
293 };
294 };
295 };
296
297 catppuccin.hyprlock.useDefaultConfig = false;
298 programs.hyprlock = {
299 enable = true;
300
301 settings = {
302 background = {
303 monitor = "";
304 path = "${config.cow.pictures.bg}";
305 blur_passes = 1;
306 };
307 shape = [
308 {
309 monitor = "";
310 color = "$crust";
311 position = "0, 30";
312 rounding = 10;
313 border_size = 2;
314 border_color = "$mauve";
315 size = "500, 500";
316 shadow_passes = 1;
317 shadow_size = 2;
318 }
319 {
320 monitor = "";
321 color = "$crust";
322 position = "0, -30";
323 rounding = 10;
324 border_size = 2;
325 border_color = "$mauve";
326 size = "600, 50";
327 valign = "top";
328 shadow_passes = 1;
329 shadow_size = 2;
330 }
331 ];
332 image = {
333 monitor = "";
334 path = "${config.cow.pictures.pfp}";
335 size = 150;
336 rounding = -1;
337 border_size = 4;
338 border_color = "$mauve";
339 rotate = 0;
340 position = "0, 120";
341 halign = "center";
342 valign = "center";
343 };
344 "input-field" = {
345 monitor = "";
346 size = "250, 50";
347 outline_thickness = 2;
348 dots_size = 0.25; # Scale of input-field height, 0.2 - 0.8
349 dots_spacing = 0.15; # Scale of dots' absolute size, 0.0 - 1.0
350 dots_center = false;
351 dots_rounding = -1; # -1 default circle, -2 follow input-field rounding
352 outer_color = "$surface0";
353 inner_color = "$base";
354 font_color = "$text";
355 fade_on_empty = false;
356 fade_timeout = 1000; # Milliseconds before fade_on_empty is triggered.
357 placeholder_text = ''<span foreground="##cdd6f4" style="italic">Password</span>'';
358 hide_input = false;
359 rounding = -1; # -1 means complete rounding (circle/oval)
360 check_color = "$peach";
361 fail_color = "$red"; # if authentication failed, changes outer_color and fail message color
362 fail_text = "<i>$FAIL <b>($ATTEMPTS)</b></i>";
363 fail_transition = 300; # transition time in ms between normal outer_color and fail_color
364 capslock_color = -1;
365 numlock_color = -1;
366 bothlock_color = -1; # when both locks are active. -1 means don't change outer color (same for above)
367 invert_numlock = false; # change color if numlock is off
368 swap_font_color = false; # see below
369
370 position = "0, -80";
371 halign = "center";
372 valign = "center";
373 };
374 label = [
375 {
376 monitor = "";
377 text = "$DESC";
378 color = "$text";
379 font_size = 25;
380 font_family = "sans-serif";
381 rotate = 0; # degrees, counter-clockwise
382
383 position = "0, 0";
384 halign = "center";
385 valign = "center";
386 }
387 {
388 monitor = "";
389 text = ''cmd[update:30000] echo " $(${pkgs.uutils-coreutils-noprefix}/bin/date +"%A, %B %-d | %I:%M %p")$(${pkgs.nushell}/bin/nu ${../res/bat_display.nu}) "'';
390 color = "$text";
391 font_size = 20;
392 font_family = "sans-serif";
393 rotate = 0; # degrees, counter-clockwise
394
395 position = "0, -40";
396 halign = "center";
397 valign = "top";
398 }
399 ];
400 };
401 };
402
403 catppuccin.rofi.enable = false;
404
405 systemd.user.services = let
406 target = config.wayland.systemd.target;
407 mkShellService = {
408 desc,
409 service,
410 }: {
411 Install = {
412 WantedBy = [target];
413 };
414
415 Unit = {
416 ConditionEnvironment = "WAYLAND_DISPLAY";
417 Description = desc;
418 After = [target];
419 PartOf = [target];
420 };
421
422 Service = service;
423 };
424 in {
425 battery-notif = mkShellService {
426 desc = "Battery Notification Service";
427
428 service = {
429 ExecStart = ''${pkgs.nushell}/bin/nu --plugins "[${
430 lib.getExe inputs.nu_plugin_dbus.packages.${pkgs.system}.default
431 }]" ${../res/battery_notif.nu}'';
432 Restart = "on-failure";
433 RestartSec = "10";
434 };
435 };
436
437 swaybg = lib.mkIf config.cow.pictures.enable (mkShellService {
438 desc = "Sway Background Image";
439 service = {
440 ExecStart = "${lib.getExe pkgs.swaybg} -m fill --image ${config.cow.pictures.bg}";
441 Restart = "on-failure";
442 RestartSec = "10";
443 };
444 });
445
446 mpris-idle-inhibit = mkShellService {
447 desc = "MPRIS Idle Inhibitor";
448
449 service = {
450 ExecStart = ''${
451 lib.getExe inputs.wayland-mpris-idle-inhibit.packages.${pkgs.system}.default
452 } --ignore "kdeconnect" --ignore "playerctld"'';
453 Restart = "on-failure";
454 RestartSec = "10";
455 };
456 };
457 };
458
459 qt = {
460 enable = true;
461 platformTheme.name = "kvantum";
462 style.name = "kvantum";
463 };
464
465 home.pointerCursor = {
466 inherit (cursorTheme) name package size;
467 enable = true;
468 gtk.enable = true;
469 x11.enable = true;
470 };
471
472 gtk = {
473 enable = true;
474 iconTheme = lib.mkForce iconTheme;
475 gtk2.extraConfig = "gtk-application-prefer-dark-theme=true";
476 gtk3.extraConfig.gtk-application-prefer-dark-theme = true;
477 gtk4.extraConfig.gtk-application-prefer-dark-theme = true;
478 };
479
480 dconf.settings."org/gnome/desktop/interface".color-scheme = "prefer-dark";
481
482 services = {
483 swaync = {
484 enable = true;
485 settings = {
486 control-center-exclusive-zone = false;
487 control-center-height = 1000;
488 control-center-margin-bottom = 10;
489 control-center-margin-left = 10;
490 control-center-margin-right = 10;
491 control-center-margin-top = 0;
492 control-center-width = 800;
493 fit-to-screen = false;
494 hide-on-action = true;
495 hide-on-clear = false;
496 image-visibility = "when-available";
497 keyboard-shortcuts = true;
498 notification-body-image-height = 100;
499 notification-body-image-width = 200;
500 notification-icon-size = 64;
501 notification-window-width = 500;
502 positionX = "center";
503 positionY = "top";
504 script-fail-notify = true;
505 scripts = {
506 all = {
507 exec = "${pkgs.nushell}/bin/nu ${../res/notification.nu} ${../res/notif-sounds}";
508 urgency = ".*";
509 };
510 };
511 timeout = 10;
512 timeout-critical = 0;
513 timeout-low = 5;
514 transition-time = 200;
515 widget-config = {
516 dnd = {
517 text = "Do Not Disturb";
518 };
519 label = {
520 max-lines = 1;
521 text = "Notification Center";
522 };
523 title = {
524 button-text = "Clear All";
525 clear-all-button = true;
526 text = "Notification Center";
527 };
528 };
529 widgets = [
530 "title"
531 "dnd"
532 "notifications"
533 ];
534 };
535 };
536
537 swayidle = let
538 lockCmd = args: "pidof hyprlock || ${lib.getExe pkgs.hyprlock} ${args} &";
539 in
540 lib.mkIf config.cow.gdi.doIdle {
541 enable = true;
542 timeouts = [
543 {
544 timeout = 120;
545 command = lockCmd "--grace 5";
546 }
547 {
548 timeout = 240;
549 command = "${pkgs.systemd}/bin/systemctl suspend";
550 }
551 ];
552 events = {
553 "before-sleep" = lockCmd "--immediate";
554 "unlock" = "pkill hyprlock --signal SIGUSR1";
555 "lock" = lockCmd "--immediate";
556 };
557 };
558
559 cliphist = {
560 enable = true;
561 systemdTargets = lib.mkForce [
562 config.wayland.systemd.target
563 ];
564 };
565 udiskie = {
566 enable = true;
567 automount = false;
568 tray = "never";
569 };
570 playerctld.enable = true;
571 wlsunset = {
572 enable = true;
573 sunrise = "6:00";
574 sunset = "22:00";
575 };
576 swayosd = {
577 enable = true;
578 stylePath = pkgs.writeText "swayosd-style.css" ''
579 window#osd {
580 border-radius: 5rem;
581 }
582
583 #container {
584 padding: 5px 10px;
585 }
586 '';
587 };
588 };
589
590 programs = {
591 rofi = {
592 enable = true;
593 package = pkgs.rofi.override {
594 plugins = with pkgs; [
595 rofi-emoji
596 rofi-power-menu
597 rofi-bluetooth
598 rofi-calc
599 rofi-pulse-select
600 ];
601 };
602 theme = let
603 inherit (config.lib.formats.rasi) mkLiteral;
604 in {
605 "@import" = "${config.catppuccin.sources.rofi}/themes/catppuccin-${config.catppuccin.rofi.flavor}.rasi";
606 "*" =
607 (builtins.mapAttrs (name: value: mkLiteral "@${value}") {
608 "bg0" = "base";
609 "bg1" = "mantle";
610 "bg2" = "crust";
611 "bg3" = config.catppuccin.accent;
612 "fg0" = "subtext1";
613 "fg1" = "text";
614 "fg2" = "subtext0";
615 "fg3" = "overlay0";
616 "fg4" = "surface0";
617 })
618 // {
619 font = mkLiteral ''"Roboto 14"'';
620 background-color = mkLiteral ''transparent'';
621 text-color = mkLiteral ''@fg0'';
622 margin = mkLiteral ''0px'';
623 padding = mkLiteral ''0px'';
624 spacing = mkLiteral ''0px'';
625 };
626 "window" = {
627 location = mkLiteral ''north'';
628 y-offset = mkLiteral ''calc(50% - 176px)'';
629 width = mkLiteral ''600'';
630 border-radius = mkLiteral ''24px'';
631 background-color = mkLiteral ''@bg0'';
632 };
633 "mainbox" = {
634 padding = mkLiteral ''12px'';
635 };
636 "inputbar" = {
637 background-color = mkLiteral ''@bg1'';
638 border-color = mkLiteral ''@bg3'';
639 border = mkLiteral ''2px'';
640 border-radius = mkLiteral ''16px'';
641 padding = mkLiteral ''8px 16px'';
642 spacing = mkLiteral ''8px'';
643 children = mkLiteral ''[ prompt, entry ]'';
644 };
645 "prompt" = {
646 text-color = mkLiteral ''@fg2'';
647 };
648 "entry" = {
649 placeholder = mkLiteral ''"Search"'';
650 placeholder-color = mkLiteral ''@fg3'';
651 };
652 "message" = {
653 margin = mkLiteral ''12px 0 0'';
654 border-radius = mkLiteral ''16px'';
655 border-color = mkLiteral ''@bg2'';
656 background-color = mkLiteral ''@bg2'';
657 };
658 "textbox" = {
659 padding = mkLiteral ''8px 24px'';
660 };
661 "listview" = {
662 background-color = mkLiteral ''transparent'';
663 margin = mkLiteral ''12px 0 0'';
664 lines = mkLiteral ''8'';
665 columns = mkLiteral ''2'';
666 fixed-height = mkLiteral ''false'';
667 };
668 "element" = {
669 padding = mkLiteral ''8px 16px'';
670 spacing = mkLiteral ''8px'';
671 border-radius = mkLiteral ''16px'';
672 };
673 "element normal active" = {
674 text-color = mkLiteral ''@bg3'';
675 };
676 "element alternate active" = {
677 text-color = mkLiteral ''@bg3'';
678 };
679 "element selected normal, element selected active" = {
680 text-color = mkLiteral ''@fg4'';
681 background-color = mkLiteral ''@bg3'';
682 };
683 "element-icon" = {
684 size = mkLiteral ''1em'';
685 vertical-align = mkLiteral ''0.5'';
686 };
687 "element-text" = {
688 text-color = mkLiteral ''inherit'';
689 };
690 };
691 location = "center";
692 };
693 nushell.extraConfig = ''
694 plugin add ${inputs.nu_plugin_dbus.packages.${pkgs.system}.default}/bin/nu_plugin_dbus
695 '';
696
697 wezterm = {
698 enable = true;
699 extraConfig = ''
700 return {
701 font = wezterm.font("monospace"),
702 font_size = 18.0,
703 color_scheme = "Catppuccin Mocha",
704 enable_tab_bar = false,
705 window_background_opacity = 0.92,
706 default_cursor_style = "SteadyBar",
707 cursor_thickness = 2,
708 keys = {
709 {key="o", mods="CTRL|SHIFT", action="OpenLinkAtMouseCursor"}
710 }
711 }
712 '';
713 };
714 };
715 };
716}