summaryrefslogtreecommitdiff
path: root/accounts/gkleen@sif/niri/default.nix
diff options
context:
space:
mode:
Diffstat (limited to 'accounts/gkleen@sif/niri/default.nix')
-rw-r--r--accounts/gkleen@sif/niri/default.nix1051
1 files changed, 579 insertions, 472 deletions
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
index 7e187c84..e4a93a49 100644
--- a/accounts/gkleen@sif/niri/default.nix
+++ b/accounts/gkleen@sif/niri/default.nix
@@ -1,6 +1,10 @@
1{ config, hostConfig, pkgs, lib, ... }: 1{ config, hostConfig, pkgs, lib, flakeInputs, ... }:
2let 2let
3 niri = config.programs.niri.package; 3 cfg = config.programs.niri;
4
5 kdl = flakeInputs.niri-flake.lib.kdl;
6
7 niri = cfg.package;
4 terminal = lib.getExe config.programs.kitty.package; 8 terminal = lib.getExe config.programs.kitty.package;
5 makoctl = lib.getExe' config.services.mako.package "makoctl"; 9 makoctl = lib.getExe' config.services.mako.package "makoctl";
6 loginctl = lib.getExe' hostConfig.systemd.package "loginctl"; 10 loginctl = lib.getExe' hostConfig.systemd.package "loginctl";
@@ -78,7 +82,7 @@ let
78 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET" 82 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
79 ''; 83 '';
80 }; 84 };
81 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^pwctl|eff|kpxc|bmgr|edit|term$"; 85 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^${lib.concatMapStringsSep "|" ({ name, ...}: name) cfg.scratchspaces}$";
82 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}''; 86 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
83 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}''; 87 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
84 88
@@ -101,6 +105,21 @@ let
101 }; 105 };
102 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace); 106 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
103 107
108 with_empty_unnamed_workspace = pkgs.writeShellApplication {
109 name = "with-empty-unnamed-workspace";
110 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
111 text = ''
112 action="$1"
113 shift
114
115 workspaces_json="$(niri msg -j workspaces)"
116 active_output="$(jq '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
117 target_workspace_id="$(jq --argjson active_output "$active_output" 'map(select(.active_window_id == null and .name == null and .output == $active_output)) | sort_by(.idx) | .[0].id' <<<"$workspaces_json")"
118 jq --argjson workspace_id "$target_workspace_id" -nc "$action" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
119 '';
120 };
121 with-empty-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_empty_unnamed_workspace);
122
104 with_select_window = pkgs.writeShellApplication { 123 with_select_window = pkgs.writeShellApplication {
105 name = "with-select-window"; 124 name = "with-select-window";
106 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ]; 125 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
@@ -129,6 +148,54 @@ in {
129 ./swayosd.nix 148 ./swayosd.nix
130 ]; 149 ];
131 150
151 options = {
152 programs.niri.scratchspaces = lib.mkOption {
153 type = lib.types.listOf (lib.types.submodule ({ config, ... }: {
154 options = {
155 name = lib.mkOption {
156 type = lib.types.str;
157 };
158 match = lib.mkOption {
159 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
160 default = [];
161 };
162 exclude = lib.mkOption {
163 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
164 default = [];
165 };
166 windowRuleExtra = lib.mkOption {
167 type = kdl.types.kdl-nodes;
168 default = [];
169 };
170 key = lib.mkOption {
171 type = lib.types.nullOr lib.types.str;
172 default = null;
173 };
174 spawn = lib.mkOption {
175 type = lib.types.nullOr (lib.types.listOf lib.types.str);
176 default = null;
177 };
178 app-id = lib.mkOption {
179 type = lib.types.nullOr lib.types.str;
180 default = null;
181 };
182 selector = lib.mkOption {
183 type = lib.types.nullOr lib.types.str;
184 default = null;
185 };
186 };
187
188 config = lib.mkMerge [
189 (lib.mkIf (config.app-id != null) {
190 match = lib.mkDefault [ { app-id = "^${lib.escapeRegex config.app-id}$"; } ];
191 selector = lib.mkDefault "select(.app_id == \"${config.app-id}\")";
192 })
193 ];
194 }));
195 default = [];
196 };
197 };
198
132 config = { 199 config = {
133 systemd.user.services.xwayland-satellite = { 200 systemd.user.services.xwayland-satellite = {
134 Unit = { 201 Unit = {
@@ -268,476 +335,516 @@ in {
268 }; 335 };
269 }; 336 };
270 337
271 programs.niri.settings = { 338 programs.niri.scratchspaces = [
272 prefer-no-csd = true; 339 { name = "pwctl";
273 screenshot-path = "${config.home.homeDirectory}/screenshots"; 340 key = "Mod+Control+A";
274 341 spawn = ["pwvucontrol"];
275 hotkey-overlay.skip-at-startup = true; 342 app-id = "com.saivert.pwvucontrol";
276 343 }
277 input = { 344 { name = "kpxc";
278 keyboard = { 345 exclude = [
279 repeat-delay = 300; 346 { title = "^Unlock Database.*"; }
280 repeat-rate = 50; 347 { title = "^Access Request.*"; }
281 348 { title = ".*Passkey credentials$"; }
282 xkb = {
283 layout = "us,us";
284 variant = "dvp,";
285 options = "compose:caps,grp:win_space_toggle";
286 };
287 };
288
289 workspace-auto-back-and-forth = true;
290 # focus-follows-mouse.enable = true;
291 warp-mouse-to-focus = true;
292 };
293
294 outputs = {
295 "eDP-1" = {
296 scale = 1.5;
297 position = { x = 0; y = 0; };
298 };
299 "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" = {
300 scale = 1.5;
301 position = { x = 2560; y = 0; };
302 };
303 "HP Inc. HP 727pu CN4417143K" = {
304 mode = { width = 2560; height = 1440; refresh = 119.998; };
305 scale = 1;
306 position = { x = 2560; y = 0; };
307 variable-refresh-rate = "on-demand";
308 };
309 };
310
311 environment = {
312 NIXOS_OZONE_WL = "1";
313 QT_QPA_PLATFORM = "wayland";
314 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
315 GDK_BACKEND = "wayland";
316 SDL_VIDEODRIVER = "wayland";
317 DISPLAY = ":0";
318 };
319
320 debug.render-drm-device = "/dev/dri/by-path/pci-0000:00:02.0-render";
321
322 animations = {
323 slowdown = 0.5;
324 workspace-switch = null;
325 };
326
327 layout = {
328 gaps = 8;
329 struts = { left = 0; right = 0; top = 0; bottom = 0; };
330 focus-ring = {
331 width = 2;
332 active.gradient = {
333 from = "hsla(195 100% 60% 0.75)";
334 to = "hsla(155 100% 50% 0.75)";
335 angle = 29;
336 relative-to = "workspace-view";
337 };
338 inactive.gradient = {
339 from = "hsla(0 0% 42% 0.66)";
340 to = "hsla(0 0% 35% 0.66)";
341 angle = 29;
342 relative-to = "workspace-view";
343 };
344 };
345
346 preset-column-widths = [
347 { proportion = 1. / 4.; }
348 { proportion = 1. / 3.; }
349 { proportion = 1. / 2.; }
350 { proportion = 2. / 3.; }
351 { proportion = 3. / 4.; }
352 ]; 349 ];
353 default-column-width.proportion = 1. / 2.; 350 windowRuleExtra = [
354 preset-window-heights = [ 351 (kdl.leaf "open-focused" false)
355 { proportion = 1. / 3.; } 352 ];
356 { proportion = 1. / 2.; } 353 key = "Mod+Control+P";
357 { proportion = 2. / 3.; } 354 app-id = "org.keepassxc.KeePassXC";
358 { proportion = 1.; } 355 spawn = [ "keepassxc" ];
356 }
357 { name = "bmgr";
358 key = "Mod+Control+B";
359 app-id = ".blueman-manager-wrapped";
360 spawn = [ "blueman-manager" ];
361 }
362 { name = "term";
363 key = "Mod+Control+Return";
364 app-id = "kitty-scratch";
365 spawn = [ "kitty" "--app-id" "kitty-scratch" ];
366 }
367 { name = "edit";
368 match = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
369 key = "Mod+Control+E";
370 selector = "select(.app_id == \"emacs\" and .title == \"scratch\")";
371 spawn = [ "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))" ];
372 }
373 { name = "eff";
374 key = "Mod+Control+O";
375 app-id = "com.github.wwmm.easyeffects";
376 spawn = [ "easyeffects" ];
377 }
378 ];
379 programs.niri.config =
380 let
381 inherit (kdl) node plain leaf flag;
382 optional-node = cond: v:
383 if cond
384 then v
385 else null;
386 opt-props = lib.filterAttrs (lib.const (value: value != null));
387 in
388 [ (flag "prefer-no-csd")
389
390 (plain "hotkey-overlay" [
391 (flag "skip-at-startup")
392 ])
393
394 (plain "input" [
395 (plain "keyboard" [
396 (leaf "repeat-delay" 300)
397 (leaf "repeat-rate" 50)
398
399 (plain "xkb" [
400 (leaf "layout" "us,us")
401 (leaf "variant" "dvp,")
402 (leaf "options" "compose:caps,grp:win_space_toggle")
403 ])
404 ])
405
406 (flag "workspace-auto-back-and-forth")
407 # (leaf "focus-follows-mouse" {})
408 # (flag "warp-mouse-to-focus")
409
410 (plain "touchpad" [ (flag "off") ])
411 (plain "trackball" [
412 (leaf "scroll-method" "on-button-down")
413 (leaf "scroll-button" 278)
414 ])
415 (plain "touch" [
416 (leaf "map-to-output" "eDP-1")
417 ])
418 ])
419
420 (plain "environment" (lib.mapAttrsToList leaf {
421 NIXOS_OZONE_WL = "1";
422 QT_QPA_PLATFORM = "wayland";
423 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
424 GDK_BACKEND = "wayland";
425 SDL_VIDEODRIVER = "wayland";
426 DISPLAY = ":0";
427 }))
428
429 (node "output" "eDP-1" [
430 (leaf "scale" 1.5)
431 (leaf "position" { x = 0; y = 0; })
432 ])
433 (node "output" "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" [
434 (leaf "scale" 1.5)
435 (leaf "position" { x = 2560; y = 0; })
436 ])
437 (node "output" "HP Inc. HP 727pu CN4417143K" [
438 (leaf "mode" "2560x1440@119.998")
439 (leaf "scale" 1)
440 (leaf "position" { x = 2560; y = 0; })
441 (flag "variable-refresh-rate")
442 ])
443
444 (plain "debug" [
445 (leaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render")
446 ])
447
448 (plain "animations" [
449 (leaf "slowdown" 0.5)
450 (plain "workspace-switch" [(flag "off")])
451 ])
452
453 (plain "layout" [
454 (leaf "gaps" 8)
455 (plain "struts" [
456 (leaf "left" 0)
457 (leaf "right" 0)
458 (leaf "top" 0)
459 (leaf "bottom" 0)
460 ])
461 (plain "border" [
462 (leaf "width" 2)
463 (leaf "active-gradient" {
464 from = "hsla(195 100% 45% 1)";
465 to = "hsla(155 100% 37.5% 1)";
466 angle = 29;
467 relative-to = "workspace-view";
468 })
469 (leaf "inactive-gradient" {
470 from = "hsla(0 0% 27.7% 1)";
471 to = "hsla(0 0% 23% 1)";
472 angle = 29;
473 relative-to = "workspace-view";
474 })
475 ])
476 (plain "focus-ring" [
477 (flag "off")
478 ])
479
480 (plain "preset-column-widths" (map (prop: leaf "proportion" prop) [
481 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.)
482 ]))
483 (plain "default-column-width" [ (leaf "proportion" (1. / 2.)) ])
484 (plain "preset-window-heights" (map (prop: leaf "proportion" prop) [
485 (1. / 3.) (1. / 2.) (2. / 3.) (1.)
486 ]))
487
488 (flag "always-center-single-column")
489
490 (plain "tab-indicator" [
491 (leaf "gap" 4)
492 (leaf "width" 8)
493 (leaf "gaps-between-tabs" 4)
494 (flag "place-within-column")
495 (leaf "length" { total-proportion = 1.; })
496 (leaf "active-gradient" {
497 from = "hsla(195 100% 60% 0.75)";
498 to = "hsla(155 100% 50% 0.75)";
499 angle = 29;
500 relative-to = "workspace-view";
501 })
502 (leaf "inactive-gradient" {
503 from = "hsla(0 0% 42% 0.66)";
504 to = "hsla(0 0% 35% 0.66)";
505 angle = 29;
506 relative-to = "workspace-view";
507 })
508 ])
509 ])
510
511 (plain "cursor" [
512 (flag "hide-when-typing")
513 ])
514
515 (map (name:
516 (node "workspace" name [
517 (leaf "open-on-output" "eDP-1")
518 ])
519 ) (map ({name, ...}: name) cfg.scratchspaces))
520 (map (name:
521 (leaf "workspace" name)
522 ) ["comm" "web" "vid" "bmr"])
523
524 (plain "window-rule" [
525 (leaf "clip-to-geometry" true)
526 ])
527
528 (plain "window-rule" [
529 (leaf "match" { is-floating = true; })
530 (leaf "geometry-corner-radius" 8)
531 (plain "shadow" [ (flag "on") ])
532 ])
533
534 (plain "window-rule" [
535 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; })
536 (leaf "block-out-from" "screencast")
537 ])
538 (plain "window-rule" [
539 (map (title:
540 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; })
541 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$"])
542 (leaf "open-focused" true)
543 (leaf "open-floating" true)
544 ])
545
546 (map ({ name, match, exclude, windowRuleExtra, ... }:
547 (optional-node (match != []) (plain "window-rule" [
548 (map (leaf "match") match)
549 (map (leaf "exclude") exclude)
550 (leaf "open-on-workspace" name)
551 (leaf "open-maximized" true)
552 windowRuleExtra
553 ]))
554 ) cfg.scratchspaces)
555
556 (plain "window-rule" [
557 (leaf "match" { app-id = "^emacs$"; })
558 (leaf "match" { app-id = "^firefox$"; })
559 (plain "default-column-width" [(leaf "proportion" (2. / 3.))])
560 ])
561 (plain "window-rule" [
562 (leaf "match" { app-id = "^kitty$"; })
563 (leaf "match" { app-id = "^kitty-play$"; })
564 (plain "default-column-width" [(leaf "proportion" (1. / 3.))])
565 ])
566
567 (plain "window-rule" [
568 (leaf "match" { app-id = "^thunderbird$"; })
569 (leaf "match" { app-id = "^Element$"; })
570 (leaf "match" { app-id = "^Rainbow$"; })
571 (leaf "open-on-workspace" "comm")
572 ])
573 (plain "window-rule" [
574 (leaf "match" { app-id = "^firefox$"; })
575 (leaf "open-on-workspace" "web")
576 (leaf "open-maximized" true)
577 ])
578 (plain "window-rule" [
579 (leaf "match" { app-id = "^mpv$"; })
580 (leaf "open-on-workspace" "vid")
581 (plain "default-column-width" [(leaf "proportion" 1.)])
582 ])
583 (plain "window-rule" [
584 (leaf "match" { app-id = "^kitty-play$"; })
585 (leaf "open-on-workspace" "vid")
586 (leaf "open-focused" false)
587 ])
588 (plain "window-rule" [
589 (leaf "match" { app-id = "^pdfpc$"; })
590 (plain "default-column-width" [(leaf "proportion" 1.)])
591 ])
592 (plain "window-rule" [
593 (leaf "match" { app-id = "^pdfpc$"; title = "^pdfpc - presentation$"; })
594 (plain "default-column-width" [(leaf "proportion" 1.)])
595 (leaf "open-fullscreen" true)
596 (leaf "open-on-workspace" "bmr")
597 (leaf "open-focused" false)
598 ])
599 (plain "window-rule" [
600 (map (leaf "match") [
601 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
602 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; }
603 { app-id = "^xdg-desktop-portal-gtk$"; }
604 ])
605 (leaf "open-floating" true)
606 ])
607 (plain "window-rule" [
608 (leaf "match" { app-id = "^org\\.pwmt\\.zathura$"; })
609 (leaf "match" { app-id = "^evince$"; })
610 (leaf "default-column-display" "tabbed")
611 ])
612
613 (plain "layer-rule" [
614 (leaf "match" { namespace = "^notifications$"; })
615 (leaf "match" { namespace = "^waybar$"; })
616 (leaf "match" { namespace = "^launcher$"; })
617 (leaf "block-out-from" "screencast")
618 ])
619
620 (plain "binds"
621 (let
622 bind = name: cfg: node name (opt-props {
623 cooldown-ms = cfg.cooldown-ms or null;
624 }
625 // (lib.optionalAttrs (!(cfg.repeat or true)) {
626 repeat = false;
627 })
628 // (lib.optionalAttrs (cfg.allow-when-locked or false) {
629 allow-when-locked = true;
630 })) (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
631 in
632 [
633 (lib.mapAttrsToList bind (with config.lib.niri.actions; {
634 "Mod+Slash".action = show-hotkey-overlay;
635
636 "Mod+Return".action = spawn terminal;
637 "Mod+Q".action = close-window;
638 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
639 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
640
641 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
642 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
643 name = "queue-yt-dlp";
644 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
645 text = ''
646 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
647 '';
648 }));
649 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
650 name = "queue-yt-dlp";
651 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
652 text = ''
653 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
654 '';
655 }));
656 "Mod+Alt+M".action = spawn (lib.getExe' pkgs.screen-message "sm") "-n" "Fira Mono" "-a" "1" "-f" "#fff" "-b" "#000";
657
658 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
659 name = "qalc-fuzzel";
660 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
661 text = ''
662 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
663 prev() {
664 FOUND=false
665 while IFS= read -r line; do
666 [[ -n "$line" ]] || continue
667 FOUND=true
668 echo "$line"
669 done < <(export LC_ALL=C.UTF-8; echo; find "$RESULTS_DIR" -type f -printf $'%T@ %p\n' | sort -n | cut -d' ' -f2- | xargs -r cat)
670 $FOUND || echo
671 }
672 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
673 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
674 QALC_RES="$FUZZEL_RES"
675 QALC_RET=0
676 else
677 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
678 QALC_RET=$?
679 fi
680 [[ -n "$QALC_RES" ]] || exit 1
681 EXISTING=false
682 set +o pipefail
683 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
684 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
685 set -o pipefail
686 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
687 set +o pipefail
688 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
689 set -o pipefail
690 cat >"$RES_FILE" <<<"$QALC_RES"
691 fi
692 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
693 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
694 notify-send "$QALC_RES"
695 '';
696 }));
697 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
698 name = "emoji-fuzzel";
699 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
700 text = ''
701 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
702 [[ -n "$FUZZEL_RES" ]] || exit 1
703 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
704 '';
705 }));
706 "Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
707 name = "screenshot";
708 runtimeInputs = with pkgs; [ grim slurp wl-clipboard-rs coreutils ];
709 text = ''
710 grim -g "$(slurp -b 00000080 -c FFFFFFFF -s 00000000 -w 1)" - \
711 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
712 | wl-copy --type image/png
713 '';
714 }));
715 "Shift+Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
716 name = "screenshot";
717 runtimeInputs = with pkgs; [ grim niri gojq wl-clipboard-rs coreutils ];
718 text = ''
719 grim -o "$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .output')" - \
720 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
721 | wl-copy --type image/png
722 '';
723 }));
724 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
725 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
726
727 "Mod+H".action = focus-column-left;
728 "Mod+T".action = focus-window-down;
729 "Mod+N".action = focus-window-up;
730 "Mod+S".action = focus-column-right;
731
732 "Mod+Shift+H".action = move-column-left;
733 "Mod+Shift+T".action = move-window-down;
734 "Mod+Shift+N".action = move-window-up;
735 "Mod+Shift+S".action = move-column-right;
736
737 "Mod+Control+H".action = focus-monitor-left;
738 "Mod+Control+T".action = focus-monitor-down;
739 "Mod+Control+N".action = focus-monitor-up;
740 "Mod+Control+S".action = focus-monitor-right;
741
742 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
743 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
744 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
745 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
746
747 "Mod+G".action = focus-adjacent-workspace "down";
748 "Mod+C".action = focus-adjacent-workspace "up";
749
750 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
751 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
752
753 "Mod+Shift+Control+G".action = move-workspace-down;
754 "Mod+Shift+Control+C".action = move-workspace-up;
755
756 "Mod+ParenLeft".action = focus-workspace "comm";
757 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
758
759 "Mod+ParenRight".action = focus-workspace "web";
760 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
761
762 "Mod+BraceRight".action = focus-workspace "read";
763 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
764
765 "Mod+BraceLeft".action = focus-workspace "mon";
766 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
767
768 "Mod+Asterisk".action = focus-workspace "vid";
769 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
770
771 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
772 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
773
774 "Mod+M".action = consume-or-expel-window-left;
775 "Mod+W".action = consume-or-expel-window-right;
776
777 "Mod+Shift+M".action = toggle-column-tabbed-display;
778
779 "Mod+R".action = switch-preset-column-width;
780 "Mod+Shift+R".action = switch-preset-window-height;
781 "Mod+F".action = center-column;
782 "Mod+Shift+F".action = maximize-column;
783 "Mod+Shift+Ctrl+F".action = fullscreen-window;
784
785 "Mod+V".action = switch-focus-between-floating-and-tiling;
786 "Mod+Shift+V".action = toggle-window-floating;
787
788 "Mod+Left".action = set-column-width "-10%";
789 "Mod+Down".action = set-window-height "-10%";
790 "Mod+Up".action = set-window-height "+10%";
791 "Mod+Right".action = set-column-width "+10%";
792
793 "Mod+Shift+Z" = {
794 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
795 allow-when-locked = true;
796 };
797 "Mod+Shift+L".action = spawn loginctl "lock-session";
798 "Mod+Shift+E".action = quit;
799 "Mod+Shift+Minus" = {
800 action = spawn systemctl "suspend";
801 allow-when-locked = true;
802 };
803 "Mod+Shift+Control+Minus" = {
804 action = spawn systemctl "hibernate";
805 allow-when-locked = true;
806 };
807 "Mod+Shift+P" = {
808 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
809 allow-when-locked = true;
810 };
811
812 "XF86MonBrightnessUp" = {
813 action = spawn swayosd-client "--brightness" "raise";
814 allow-when-locked = true;
815 };
816 "XF86MonBrightnessDown" = {
817 action = spawn swayosd-client "--brightness" "lower";
818 allow-when-locked = true;
819 };
820 "XF86AudioRaiseVolume" = {
821 action = spawn swayosd-client "--output-volume" "raise";
822 allow-when-locked = true;
823 };
824 "XF86AudioLowerVolume" = {
825 action = spawn swayosd-client "--output-volume" "lower";
826 allow-when-locked = true;
827 };
828 "XF86AudioMute" = {
829 action = spawn swayosd-client "--output-volume" "mute-toggle";
830 allow-when-locked = true;
831 };
832 "XF86AudioMicMute" = {
833 action = spawn swayosd-client "--input-volume" "mute-toggle";
834 allow-when-locked = true;
835 };
836
837 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
838 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
839 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
840 "Mod+Comma".action = spawn makoctl "restore";
841
842 "Mod+Control+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"FocusWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
843 "Mod+Control+Shift+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"MoveColumnToWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
844 }))
845 (map ({ name, selector, spawn, key, ...}: if key != null && selector != null && spawn != null then bind key { action = focus-or-spawn-action selector name spawn; } else null) cfg.scratchspaces)
846 ]
847 ))
359 ]; 848 ];
360
361 always-center-single-column = true;
362 };
363
364 cursor.hide-when-typing = true;
365
366 input = {
367 touchpad.enable = false;
368 trackball = {
369 scroll-method = "on-button-down";
370 scroll-button = 278;
371 };
372 };
373
374 workspaces = {
375 "001" = { name = "pwctl"; open-on-output = "eDP-1"; };
376 "002" = { name = "kpxc"; open-on-output = "eDP-1"; };
377 "003" = { name = "bmgr"; open-on-output = "eDP-1"; };
378 "004" = { name = "term"; open-on-output = "eDP-1"; };
379 "005" = { name = "edit"; open-on-output = "eDP-1"; };
380 "006" = { name = "eff"; open-on-output = "eDP-1"; };
381 "101".name = "comm";
382 "102".name = "web";
383 # "104".name = "read";
384 # "105".name = "mon";
385 "110".name = "vid";
386 "120".name = "bmr";
387 };
388
389 window-rules = [
390 {
391 matches = [ { is-floating = true; } ];
392 geometry-corner-radius =
393 let
394 allCorners = r: { bottom-left = r; bottom-right = r; top-left = r; top-right = r; };
395 in allCorners 8.;
396 clip-to-geometry = true;
397 }
398 {
399 matches = [ { app-id = "^com\.saivert\.pwvucontrol$"; } ];
400 open-on-workspace = "pwctl";
401 open-maximized = true;
402 }
403 {
404 matches = [ { app-id = "^com\.github\.wwmm\.easyeffects$"; } ];
405 open-on-workspace = "eff";
406 open-maximized = true;
407 }
408 {
409 matches = [ { app-id = "^\.blueman-manager-wrapped$"; } ];
410 open-on-workspace = "bmgr";
411 open-maximized = true;
412 }
413 {
414 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
415 block-out-from = "screencast";
416 }
417 {
418 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
419 excludes = [
420 { title = "^Unlock Database.*"; }
421 { title = "^Access Request.*"; }
422 { title = ".*Passkey credentials$"; }
423 ];
424 open-on-workspace = "kpxc";
425 open-maximized = true;
426 open-focused = false;
427 }
428 {
429 matches = [
430 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Unlock Database.*"; }
431 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Access Request.*"; }
432 { app-id = "^org\.keepassxc\.KeePassXC$"; title = ".*Passkey credentials$"; }
433 ];
434 open-focused = true;
435 open-floating = true;
436 }
437 {
438 matches = [ { app-id = "^kitty-scratch$"; } ];
439 open-on-workspace = "term";
440 open-maximized = true;
441 }
442 {
443 matches = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
444 open-on-workspace = "edit";
445 open-maximized = true;
446 }
447 {
448 matches = [
449 { app-id = "^emacs$"; }
450 { app-id = "^firefox$"; }
451 ];
452 default-column-width.proportion = 2. / 3.;
453 }
454 {
455 matches = [
456 { app-id = "^kitty$"; }
457 { app-id = "^kitty-play$"; }
458 ];
459 default-column-width.proportion = 1. / 3.;
460 }
461 {
462 matches = [
463 { app-id = "^thunderbird$"; }
464 { app-id = "^Element$"; }
465 { app-id = "^Rainbow$"; }
466 ];
467 open-on-workspace = "comm";
468 }
469 {
470 matches = [ { app-id = "^firefox$"; } ];
471 open-on-workspace = "web";
472 open-maximized = true;
473 variable-refresh-rate = true;
474 }
475 # {
476 # matches = [
477 # { app-id = "^evince$"; }
478 # { app-id = "^imv$"; }
479 # { app-id = "^org\.pwmt\.zathura$"; }
480 # ];
481 # open-on-workspace = "read";
482 # }
483 {
484 matches = [ { app-id = "^mpv$"; } ];
485 open-on-workspace = "vid";
486 default-column-width.proportion = 1.;
487 variable-refresh-rate = true;
488 }
489 {
490 matches = [ { app-id = "^kitty-play$"; } ];
491 open-on-workspace = "vid";
492 open-focused = false;
493 }
494 # {
495 # matches = [
496 # { app-id = "^qemu$"; }
497 # { app-id = "^virt-manager$"; }
498 # ];
499 # open-on-workspace = "mon";
500 # }
501 {
502 matches = [ { app-id = "^pdfpc$"; } ];
503 default-column-width.proportion = 1.;
504 }
505 {
506 matches = [ { app-id = "^pdfpc$"; title = "^pdfpc - presentation"; } ];
507 open-on-workspace = "bmr";
508 open-fullscreen = true;
509 }
510 {
511 matches = [
512 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
513 { app-id = "^org\.kde\.polkit-kde-authentication-agent-1$"; }
514 { app-id = "^xdg-desktop-portal-gtk$"; }
515 ];
516 open-floating = true;
517 }
518 ];
519 layer-rules = [
520 { matches = [
521 { namespace = "^notifications$"; }
522 { namespace = "^waybar$"; }
523 ];
524 block-out-from = "screencast";
525 }
526 ];
527
528 binds = with config.lib.niri.actions; {
529 "Mod+Slash".action = show-hotkey-overlay;
530
531 "Mod+Return".action = spawn terminal;
532 "Mod+Q".action = close-window;
533 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
534 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
535
536 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
537 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
538 name = "queue-yt-dlp";
539 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
540 text = ''
541 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
542 '';
543 }));
544 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
545 name = "queue-yt-dlp";
546 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
547 text = ''
548 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
549 '';
550 }));
551
552 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
553 name = "qalc-fuzzel";
554 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
555 text = ''
556 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
557 prev() {
558 FOUND=false
559 while IFS= read -r line; do
560 [[ -n "$line" ]] || continue
561 FOUND=true
562 echo "$line"
563 done < <(export LC_ALL=C.UTF-8; echo; find "$RESULTS_DIR" -type f -printf $'%T@ %p\n' | sort -n | cut -d' ' -f2- | xargs -r cat)
564 $FOUND || echo
565 }
566 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
567 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
568 QALC_RES="$FUZZEL_RES"
569 QALC_RET=0
570 else
571 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
572 QALC_RET=$?
573 fi
574 [[ -n "$QALC_RES" ]] || exit 1
575 EXISTING=false
576 set +o pipefail
577 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
578 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
579 set -o pipefail
580 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
581 set +o pipefail
582 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
583 set -o pipefail
584 cat >"$RES_FILE" <<<"$QALC_RES"
585 fi
586 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
587 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
588 notify-send "$QALC_RES"
589 '';
590 }));
591 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
592 name = "emoji-fuzzel";
593 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
594 text = ''
595 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
596 [[ -n "$FUZZEL_RES" ]] || exit 1
597 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
598 '';
599 }));
600 "Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
601 name = "screenshot";
602 runtimeInputs = with pkgs; [ grim slurp wl-clipboard-rs coreutils ];
603 text = ''
604 grim -g "$(slurp -b 00000080 -c FFFFFFFF -s 00000000 -w 1)" - \
605 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
606 | wl-copy --type image/png
607 '';
608 }));
609 "Shift+Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
610 name = "screenshot";
611 runtimeInputs = with pkgs; [ grim niri gojq wl-clipboard-rs coreutils ];
612 text = ''
613 grim -o "$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .output')" - \
614 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
615 | wl-copy --type image/png
616 '';
617 }));
618 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
619 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
620
621 "Mod+H".action = focus-column-left;
622 "Mod+T".action = focus-window-down;
623 "Mod+N".action = focus-window-up;
624 "Mod+S".action = focus-column-right;
625
626 "Mod+Shift+H".action = move-column-left;
627 "Mod+Shift+T".action = move-window-down;
628 "Mod+Shift+N".action = move-window-up;
629 "Mod+Shift+S".action = move-column-right;
630
631 "Mod+Control+H".action = focus-monitor-left;
632 "Mod+Control+T".action = focus-monitor-down;
633 "Mod+Control+N".action = focus-monitor-up;
634 "Mod+Control+S".action = focus-monitor-right;
635
636 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
637 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
638 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
639 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
640
641 "Mod+G".action = focus-adjacent-workspace "down";
642 "Mod+C".action = focus-adjacent-workspace "up";
643
644 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
645 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
646
647 "Mod+Shift+Control+G".action = move-workspace-down;
648 "Mod+Shift+Control+C".action = move-workspace-up;
649
650 "Mod+ParenLeft".action = focus-workspace "comm";
651 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
652
653 "Mod+ParenRight".action = focus-workspace "web";
654 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
655
656 "Mod+BraceRight".action = focus-workspace "read";
657 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
658
659 "Mod+BraceLeft".action = focus-workspace "mon";
660 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
661
662 "Mod+Asterisk".action = focus-workspace "vid";
663 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
664
665 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
666 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
667
668 "Mod+M".action = consume-or-expel-window-left;
669 "Mod+W".action = consume-or-expel-window-right;
670
671 "Mod+R".action = switch-preset-column-width;
672 "Mod+Shift+R".action = switch-preset-window-height;
673 "Mod+F".action = center-column;
674 "Mod+Shift+F".action = maximize-column;
675 "Mod+Shift+Ctrl+F".action = fullscreen-window;
676
677 "Mod+V".action = switch-focus-between-floating-and-tiling;
678 "Mod+Shift+V".action = toggle-window-floating;
679
680 "Mod+Left".action = set-column-width "-10%";
681 "Mod+Down".action = set-window-height "-10%";
682 "Mod+Up".action = set-window-height "+10%";
683 "Mod+Right".action = set-column-width "+10%";
684
685 "Mod+Shift+Z" = {
686 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
687 allow-when-locked = true;
688 };
689 "Mod+Shift+L".action = spawn loginctl "lock-session";
690 "Mod+Shift+E".action = quit;
691 "Mod+Shift+Minus" = {
692 action = spawn systemctl "suspend";
693 allow-when-locked = true;
694 };
695 "Mod+Shift+Control+Minus" = {
696 action = spawn systemctl "hibernate";
697 allow-when-locked = true;
698 };
699 "Mod+Shift+P" = {
700 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
701 allow-when-locked = true;
702 };
703
704 "XF86MonBrightnessUp" = {
705 action = spawn swayosd-client "--brightness" "raise";
706 allow-when-locked = true;
707 };
708 "XF86MonBrightnessDown" = {
709 action = spawn swayosd-client "--brightness" "lower";
710 allow-when-locked = true;
711 };
712 "XF86AudioRaiseVolume" = {
713 action = spawn swayosd-client "--output-volume" "raise";
714 allow-when-locked = true;
715 };
716 "XF86AudioLowerVolume" = {
717 action = spawn swayosd-client "--output-volume" "lower";
718 allow-when-locked = true;
719 };
720 "XF86AudioMute" = {
721 action = spawn swayosd-client "--output-volume" "mute-toggle";
722 allow-when-locked = true;
723 };
724 "XF86AudioMicMute" = {
725 action = spawn swayosd-client "--input-volume" "mute-toggle";
726 allow-when-locked = true;
727 };
728
729 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
730 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
731 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
732 "Mod+Comma".action = spawn makoctl "restore";
733
734 "Mod+Control+A".action = focus-or-spawn-action-app_id "com.saivert.pwvucontrol" "pwctl" "pwvucontrol";
735 "Mod+Control+O".action = focus-or-spawn-action-app_id "com.github.wwmm.easyeffects" "eff" "easyeffects";
736 "Mod+Control+P".action = focus-or-spawn-action-app_id "org.keepassxc.KeePassXC" "kpxc" "keepassxc";
737 "Mod+Control+B".action = focus-or-spawn-action-app_id ".blueman-manager-wrapped" "bmgr" "blueman-manager";
738 "Mod+Control+Return".action = focus-or-spawn-action-app_id "kitty-scratch" "term" "kitty" "--app-id" "kitty-scratch";
739 "Mod+Control+E".action = focus-or-spawn-action "select(.app_id == \"emacs\" and .title == \"scratch\")" "edit" "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))";
740 };
741 };
742 }; 849 };
743} 850}