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.nix843
1 files changed, 0 insertions, 843 deletions
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
deleted file mode 100644
index b5347d6f..00000000
--- a/accounts/gkleen@sif/niri/default.nix
+++ /dev/null
@@ -1,843 +0,0 @@
1{ config, hostConfig, pkgs, lib, flakeInputs, ... }:
2let
3 cfg = config.programs.niri;
4
5 kdl = flakeInputs.niri-flake.lib.kdl;
6
7 niri = cfg.package;
8 terminal = lib.getExe config.programs.kitty.package;
9 makoctl = lib.getExe' config.services.mako.package "makoctl";
10 loginctl = lib.getExe' hostConfig.systemd.package "loginctl";
11 systemctl = lib.getExe' hostConfig.systemd.package "systemctl";
12 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
13
14 focus_or_spawn = pkgs.writeShellApplication {
15 name = "focus-or-spawn";
16 runtimeInputs = [ niri pkgs.gojq pkgs.gnugrep pkgs.socat ];
17 text = ''
18 window_select="$1"
19 shift
20 workspace_name="$1"
21 shift
22
23 workspaces_json="$(niri msg -j workspaces)"
24 workspace_output="$(jq -r --arg workspace_name "$workspace_name" '.[] | select(.name == $workspace_name) | .output' <<<"$workspaces_json")"
25 # active_workspace="$(jq -r --arg workspace_output "$workspace_output" '.[] | select(.output == $workspace_output and .is_active) | .id' <<<"$workspaces_json")"
26 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
27 if [[ $workspace_output != "$active_output" ]]; then
28 niri msg action move-workspace-to-monitor --reference "$workspace_name" "$active_output"
29 # socat STDIO "$NIRI_SOCKET" <<<'{"Action":{"FocusWorkspace":{"reference":{"Id":'"''${active_workspace}"'}}}}'
30 # niri msg action move-workspace-to-index --reference "$workspace_name" 1
31 fi
32
33 while IFS=$'\n' read -r window_json; do
34 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then
35 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then
36 niri msg action focus-workspace-previous
37 else
38 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
39 fi
40 exit 0
41 fi
42 done < <(niri msg -j windows | jq -c '.[]')
43
44 exec "$@"
45 '';
46 };
47 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn);
48 focus-or-spawn-action-app_id = app_id: focus-or-spawn-action ''select(.app_id == "${app_id}")'';
49
50 with_adjacent_workspace = pkgs.writeShellApplication {
51 name = "with-adjacent-workspace";
52 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
53 text = ''
54 blacklist="$1"
55 shift
56 direction="$1"
57 shift
58 action="$1"
59 shift
60
61 workspaces_json="$(niri msg -j workspaces)"
62 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
63 workspace_output="$(jq -r --arg active_workspace "$active_workspace" '.[] | select(.id == ($active_workspace | tonumber)) | .output' <<<"$workspaces_json")"
64 workspace_idx="$(jq -r '.[] | select(.is_focused) | .idx' <<<"$workspaces_json")"
65
66 jq_script='map(select('
67 case "$direction" in
68 down)
69 # shellcheck disable=SC2016
70 jq_script=''${jq_script}'.idx > ($workspace_idx | tonumber)';;
71 up)
72 # shellcheck disable=SC2016
73 jq_script=''${jq_script}'.idx < ($workspace_idx | tonumber)';;
74 esac
75 # shellcheck disable=SC2016
76 jq_script=''${jq_script}' and .output == $workspace_output and ((.name == null) or (.name | test($blacklist) | not)))) | sort_by(.idx)'
77 [[ $direction == "up" ]] && jq_script=''${jq_script}' | reverse'
78 jq_script=''${jq_script}' | .[0]'
79
80 workspace_json=$(jq -c --arg blacklist "$blacklist" --arg workspace_output "$workspace_output" --arg workspace_idx "$workspace_idx" "$jq_script" <<<"$workspaces_json")
81 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
82 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
83 '';
84 };
85 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^${lib.concatMapStringsSep "|" ({ name, ...}: name) cfg.scratchspaces}$";
86 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
87 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
88
89 with_unnamed_workspace = pkgs.writeShellApplication {
90 name = "with-unnamed-workspace";
91 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
92 text = ''
93 action="$1"
94 shift
95
96 workspaces_json="$(niri msg -j workspaces)"
97 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
98 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
99
100 history_json="$(socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/niri-workspace-history.sock)"
101 workspace_json="$(jq -c --arg active_output "$active_output" --argjson history "$history_json" 'map(select(.output == $active_output and .name == null)) | map({"value": ., "history_idx": ((. as $workspace | ($history[$active_output] | index($workspace | .id))) as $active_idx | if $active_idx then $active_idx else ($history[$active_output] | length) + 1 end)}) | sort_by(.history_idx, .value.idx) | map(.value) | .[0]' <<<"$workspaces_json")"
102 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
103 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
104 '';
105 };
106 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
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
123 with_select_window = pkgs.writeShellApplication {
124 name = "with-select-window";
125 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
126 text = ''
127 window_select="$1"
128 shift
129 action="$1"
130 shift
131
132 windows_json="$(niri msg -j windows)"
133 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
134 window_ix="$(gojq -r --arg active_workspace "$active_workspace" '.[] | select('"$window_select"') | "\(.title)\u0000icon\u001f\(.app_id)"' <<<"$windows_json" | fuzzel --log-level=warning --dmenu --index)"
135 # shellcheck disable=SC2016
136 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")"
137
138 [[ -z "$window_json" ]] && exit 1
139
140 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
141 '';
142 };
143 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
144in {
145 imports = [
146 ./waybar.nix
147 ./mako.nix
148 ./swayosd.nix
149 ];
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
199 config = {
200 systemd.user.services.xwayland-satellite = {
201 Unit = {
202 BindsTo = [ "graphical-session.target" ];
203 PartOf = [ "graphical-session.target" ];
204 After = [ "graphical-session.target" ];
205 Requisite = [ "graphical-session.target" ];
206 };
207 Service = {
208 Type = "notify";
209 NotifyAccess = "all";
210 Environment = [ "DISPLAY=:0" ];
211 ExecStart = ''${lib.getExe pkgs.xwayland-satellite-unstable} ''${DISPLAY}'';
212 ExecStartPre = "${systemctl} --user import-environment DISPLAY";
213 StandardOutput = "journal";
214 };
215 Install = {
216 WantedBy = [ "graphical-session.target" ];
217 };
218 };
219
220 services.swayidle = {
221 events = [
222 { event = "after-resume"; command = "${lib.getExe niri} msg action power-on-monitors"; }
223 ];
224 timeouts = [
225 { timeout = 540;
226 command = "${lib.getExe niri} msg action power-off-monitors";
227 }
228 ];
229 };
230
231 systemd.user.sockets.niri-workspace-history = {
232 Socket = {
233 ListenStream = "%t/niri-workspace-history.sock";
234 SocketMode = "0600";
235 };
236 };
237 systemd.user.services.niri-workspace-history = {
238 Unit = {
239 BindsTo = [ "niri.service" ];
240 After = [ "niri.service" ];
241 };
242 Install = {
243 WantedBy = [ "niri.service" ];
244 };
245 Service = {
246 Type = "simple";
247 Sockets = [ "niri-workspace-history.socket" ];
248 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" {} ''
249 import os
250 import socket
251 import json
252 import sys
253 from collections import defaultdict
254 from threading import Thread, Lock
255 from socketserver import StreamRequestHandler, ThreadingTCPServer
256 from contextlib import contextmanager
257 from io import TextIOWrapper
258
259
260 @contextmanager
261 def detaching(thing):
262 try:
263 yield thing
264 finally:
265 thing.detach()
266
267
268 workspace_history = defaultdict(list)
269 history_lock = Lock()
270
271
272 def monitor_niri():
273 workspaces = list()
274
275 def focus_workspace(output, workspace):
276 global workspace_history, history_lock
277
278 with history_lock:
279 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace] # noqa: E501
280 print(json.dumps(workspace_history), file=sys.stderr)
281
282 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
283 sock.connect(os.environ["NIRI_SOCKET"])
284 sock.send(b"\"EventStream\"\n")
285 for line in sock.makefile(buffering=1, encoding='utf-8'):
286 if line_json := json.loads(line):
287 if "WorkspacesChanged" in line_json:
288 workspaces = line_json["WorkspacesChanged"]["workspaces"]
289 for ws in workspaces:
290 if ws["is_focused"]:
291 focus_workspace(ws["output"], ws["id"])
292 if "WorkspaceActivated" in line_json:
293 for ws in workspaces:
294 if ws["id"] != line_json["WorkspaceActivated"]["id"]:
295 continue
296 focus_workspace(ws["output"], ws["id"])
297 break
298
299
300 class RequestHandler(StreamRequestHandler):
301 def handle(self):
302 global workspace_history, history_lock
303
304 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out: # noqa: E501
305 with history_lock:
306 json.dump(workspace_history, out)
307
308
309 class Server(ThreadingTCPServer):
310 def __init__(self):
311 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False) # noqa: E501
312 self.socket = socket.fromfd(3, self.address_family, self.socket_type)
313
314
315 def run_server():
316 with Server() as server:
317 server.serve_forever()
318
319
320 niri = Thread(target=monitor_niri)
321 niri.daemon = True
322 niri.start()
323
324 server_thread = Thread(target=run_server)
325 server_thread.daemon = True
326 server_thread.start()
327
328 while True:
329 server_thread.join(timeout=0.5)
330 niri.join(timeout=0.5)
331
332 if not (niri.is_alive() and server_thread.is_alive()):
333 break
334 '';
335 };
336 };
337
338 programs.niri.scratchspaces = [
339 { name = "pwctl";
340 key = "Mod+Control+A";
341 spawn = ["pwvucontrol"];
342 app-id = "com.saivert.pwvucontrol";
343 }
344 { name = "kpxc";
345 exclude = [
346 { title = "^Unlock Database.*"; }
347 { title = "^Access Request.*"; }
348 { title = ".*Passkey credentials$"; }
349 ];
350 windowRuleExtra = [
351 (kdl.leaf "open-focused" false)
352 ];
353 key = "Mod+Control+P";
354 app-id = "org.keepassxc.KeePassXC";
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 (leaf "screenshot-path" "~/screenshots/%Y-%m-%dT%H:%M:%S.png")
391
392 (plain "hotkey-overlay" [
393 (flag "skip-at-startup")
394 ])
395
396 (plain "input" [
397 (plain "keyboard" [
398 (leaf "repeat-delay" 300)
399 (leaf "repeat-rate" 50)
400
401 (plain "xkb" [
402 (leaf "layout" "us,us")
403 (leaf "variant" "dvp,")
404 (leaf "options" "compose:caps,grp:win_space_toggle")
405 ])
406 ])
407
408 (flag "workspace-auto-back-and-forth")
409 # (leaf "focus-follows-mouse" {})
410 # (flag "warp-mouse-to-focus")
411
412 # (plain "touchpad" [ (flag "off") ])
413 (plain "trackball" [
414 (leaf "scroll-method" "on-button-down")
415 (leaf "scroll-button" 278)
416 ])
417 (plain "touch" [
418 (leaf "map-to-output" "eDP-1")
419 ])
420 ])
421
422 (plain "environment" (lib.mapAttrsToList leaf {
423 NIXOS_OZONE_WL = "1";
424 QT_QPA_PLATFORM = "wayland";
425 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
426 GDK_BACKEND = "wayland";
427 SDL_VIDEODRIVER = "wayland";
428 DISPLAY = ":0";
429 }))
430
431 (node "output" "eDP-1" [
432 (leaf "scale" 1.5)
433 (leaf "position" { x = 0; y = 0; })
434 ])
435 (node "output" "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" [
436 (leaf "scale" 1.5)
437 (leaf "position" { x = 2560; y = 0; })
438 ])
439 (node "output" "HP Inc. HP 727pu CN4417143K" [
440 (leaf "mode" "2560x1440@119.998")
441 (leaf "scale" 1)
442 (leaf "position" { x = 2560; y = 0; })
443 (flag "variable-refresh-rate")
444 ])
445
446 (plain "debug" [
447 (leaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render")
448 ])
449
450 (plain "animations" [
451 (leaf "slowdown" 0.5)
452 (plain "workspace-switch" [(flag "off")])
453 ])
454
455 (plain "layout" [
456 (leaf "gaps" 8)
457 (plain "struts" [
458 (leaf "left" 26)
459 (leaf "right" 26)
460 (leaf "top" 0)
461 (leaf "bottom" 0)
462 ])
463 (plain "border" [
464 (leaf "width" 2)
465 (leaf "active-gradient" {
466 from = "hsla(195 100% 45% 1)";
467 to = "hsla(155 100% 37.5% 1)";
468 angle = 29;
469 relative-to = "workspace-view";
470 })
471 (leaf "inactive-gradient" {
472 from = "hsla(0 0% 27.7% 1)";
473 to = "hsla(0 0% 23% 1)";
474 angle = 29;
475 relative-to = "workspace-view";
476 })
477 ])
478 (plain "focus-ring" [
479 (flag "off")
480 ])
481
482 (plain "preset-column-widths" (map (prop: leaf "proportion" prop) [
483 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.)
484 ]))
485 (plain "default-column-width" [ (leaf "proportion" (1. / 2.)) ])
486 (plain "preset-window-heights" (map (prop: leaf "proportion" prop) [
487 (1. / 3.) (1. / 2.) (2. / 3.) (1.)
488 ]))
489
490 (flag "always-center-single-column")
491
492 (plain "tab-indicator" [
493 (leaf "gap" 4)
494 (leaf "width" 8)
495 (leaf "gaps-between-tabs" 4)
496 (flag "place-within-column")
497 (leaf "length" { total-proportion = 1.; })
498 (leaf "active-gradient" {
499 from = "hsla(195 100% 60% 0.75)";
500 to = "hsla(155 100% 50% 0.75)";
501 angle = 29;
502 relative-to = "workspace-view";
503 })
504 (leaf "inactive-gradient" {
505 from = "hsla(0 0% 42% 0.66)";
506 to = "hsla(0 0% 35% 0.66)";
507 angle = 29;
508 relative-to = "workspace-view";
509 })
510 ])
511 ])
512
513 (plain "cursor" [
514 (flag "hide-when-typing")
515 ])
516
517 (map (name:
518 (node "workspace" name [
519 (leaf "open-on-output" "eDP-1")
520 ])
521 ) (map ({name, ...}: name) cfg.scratchspaces))
522 (map (name:
523 (leaf "workspace" name)
524 ) ["comm" "web" "vid" "bmr"])
525
526 (plain "window-rule" [
527 (leaf "clip-to-geometry" true)
528 ])
529
530 (plain "window-rule" [
531 (leaf "match" { is-floating = true; })
532 (leaf "geometry-corner-radius" 8)
533 (plain "shadow" [ (flag "on") ])
534 ])
535
536 (plain "window-rule" [
537 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; })
538 (leaf "block-out-from" "screencast")
539 ])
540 (plain "window-rule" [
541 (map (title:
542 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; })
543 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$"])
544 (leaf "open-focused" true)
545 (leaf "open-floating" true)
546 ])
547
548 (map ({ name, match, exclude, windowRuleExtra, ... }:
549 (optional-node (match != []) (plain "window-rule" [
550 (map (leaf "match") match)
551 (map (leaf "exclude") exclude)
552 (leaf "open-on-workspace" name)
553 (leaf "open-maximized" true)
554 windowRuleExtra
555 ]))
556 ) cfg.scratchspaces)
557
558 (plain "window-rule" [
559 (leaf "match" { app-id = "^emacs$"; })
560 (leaf "match" { app-id = "^firefox$"; })
561 (plain "default-column-width" [(leaf "proportion" (2. / 3.))])
562 ])
563 (plain "window-rule" [
564 (leaf "match" { app-id = "^kitty$"; })
565 (leaf "match" { app-id = "^kitty-play$"; })
566 (plain "default-column-width" [(leaf "proportion" (1. / 3.))])
567 ])
568
569 (plain "window-rule" [
570 (leaf "match" { app-id = "^thunderbird$"; })
571 (leaf "match" { app-id = "^Element$"; })
572 (leaf "match" { app-id = "^Rainbow$"; })
573 (leaf "open-on-workspace" "comm")
574 ])
575 (plain "window-rule" [
576 (leaf "match" { app-id = "^firefox$"; })
577 (leaf "open-on-workspace" "web")
578 (leaf "open-maximized" true)
579 ])
580 (plain "window-rule" [
581 (leaf "match" { app-id = "^mpv$"; })
582 (leaf "open-on-workspace" "vid")
583 (plain "default-column-width" [(leaf "proportion" 1.)])
584 ])
585 (plain "window-rule" [
586 (leaf "match" { app-id = "^kitty-play$"; })
587 (leaf "open-on-workspace" "vid")
588 (leaf "open-focused" false)
589 ])
590 (plain "window-rule" [
591 (leaf "match" { app-id = "^pdfpc$"; })
592 (plain "default-column-width" [(leaf "proportion" 1.)])
593 ])
594 (plain "window-rule" [
595 (leaf "match" { app-id = "^pdfpc$"; title = "^pdfpc - presentation$"; })
596 (plain "default-column-width" [(leaf "proportion" 1.)])
597 (leaf "open-fullscreen" true)
598 (leaf "open-on-workspace" "bmr")
599 (leaf "open-focused" false)
600 ])
601 (plain "window-rule" [
602 (map (leaf "match") [
603 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
604 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; }
605 { app-id = "^xdg-desktop-portal-gtk$"; }
606 ])
607 (leaf "open-floating" true)
608 ])
609 (plain "window-rule" [
610 (leaf "match" { app-id = "^org\\.pwmt\\.zathura$"; })
611 (leaf "match" { app-id = "^evince$"; })
612 (leaf "match" { app-id = "^org\\.gnome\\.Papers$"; })
613 (leaf "default-column-display" "tabbed")
614 ])
615
616 (plain "layer-rule" [
617 (leaf "match" { namespace = "^notifications$"; })
618 (leaf "match" { namespace = "^waybar$"; })
619 (leaf "match" { namespace = "^launcher$"; })
620 (leaf "block-out-from" "screencast")
621 ])
622
623 (plain "binds"
624 (let
625 bind = name: cfg: node name (opt-props {
626 cooldown-ms = cfg.cooldown-ms or null;
627 }
628 // (lib.optionalAttrs (!(cfg.repeat or true)) {
629 repeat = false;
630 })
631 // (lib.optionalAttrs (cfg.allow-when-locked or false) {
632 allow-when-locked = true;
633 })) (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
634 in
635 [
636 (lib.mapAttrsToList bind (with config.lib.niri.actions; {
637 "Mod+Slash".action = show-hotkey-overlay;
638
639 "Mod+Return".action = spawn terminal;
640 "Mod+Q".action = close-window;
641 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
642 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
643
644 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
645 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
646 name = "queue-yt-dlp";
647 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
648 text = ''
649 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
650 '';
651 }));
652 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
653 name = "queue-yt-dlp";
654 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
655 text = ''
656 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
657 '';
658 }));
659 "Mod+Alt+M".action = spawn (lib.getExe' pkgs.screen-message "sm") "-n" "Fira Mono" "-a" "1" "-f" "#fff" "-b" "#000";
660
661 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
662 name = "qalc-fuzzel";
663 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
664 text = ''
665 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
666 prev() {
667 FOUND=false
668 while IFS= read -r line; do
669 [[ -n "$line" ]] || continue
670 FOUND=true
671 echo "$line"
672 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)
673 $FOUND || echo
674 }
675 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
676 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
677 QALC_RES="$FUZZEL_RES"
678 QALC_RET=0
679 else
680 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
681 QALC_RET=$?
682 fi
683 [[ -n "$QALC_RES" ]] || exit 1
684 EXISTING=false
685 set +o pipefail
686 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
687 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
688 set -o pipefail
689 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
690 set +o pipefail
691 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
692 set -o pipefail
693 cat >"$RES_FILE" <<<"$QALC_RES"
694 fi
695 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
696 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
697 notify-send "$QALC_RES"
698 '';
699 }));
700 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
701 name = "emoji-fuzzel";
702 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
703 text = ''
704 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
705 [[ -n "$FUZZEL_RES" ]] || exit 1
706 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
707 '';
708 }));
709 "Print".action = screenshot;
710 "Control+Print".action = screenshot-window;
711 # "Shift+Print".action = screenshot-screen;
712 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
713 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
714
715 "Mod+Escape" = {
716 allow-inhibiting = false;
717 action = toggle-keyboard-shortcuts-inhibit;
718 };
719
720 "Mod+H".action = focus-column-left;
721 "Mod+T".action = focus-window-down;
722 "Mod+N".action = focus-window-up;
723 "Mod+S".action = focus-column-right;
724
725 "Mod+Shift+H".action = move-column-left;
726 "Mod+Shift+T".action = move-window-down;
727 "Mod+Shift+N".action = move-window-up;
728 "Mod+Shift+S".action = move-column-right;
729
730 "Mod+Control+H".action = focus-monitor-left;
731 "Mod+Control+T".action = focus-monitor-down;
732 "Mod+Control+N".action = focus-monitor-up;
733 "Mod+Control+S".action = focus-monitor-right;
734
735 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
736 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
737 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
738 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
739
740 "Mod+G".action = focus-adjacent-workspace "down";
741 "Mod+C".action = focus-adjacent-workspace "up";
742
743 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
744 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
745
746 "Mod+Shift+Control+G".action = move-workspace-down;
747 "Mod+Shift+Control+C".action = move-workspace-up;
748
749 "Mod+ParenLeft".action = focus-workspace "comm";
750 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
751
752 "Mod+ParenRight".action = focus-workspace "web";
753 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
754
755 "Mod+BraceRight".action = focus-workspace "read";
756 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
757
758 "Mod+BraceLeft".action = focus-workspace "mon";
759 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
760
761 "Mod+Asterisk".action = focus-workspace "vid";
762 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
763
764 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
765 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
766
767 "Mod+M".action = consume-or-expel-window-left;
768 "Mod+W".action = consume-or-expel-window-right;
769
770 "Mod+Shift+M".action = toggle-column-tabbed-display;
771
772 "Mod+R".action = switch-preset-column-width;
773 "Mod+Shift+R".action = switch-preset-window-height;
774 "Mod+F".action = center-column;
775 "Mod+Shift+F".action = maximize-column;
776 "Mod+Shift+Ctrl+F".action = fullscreen-window;
777
778 "Mod+V".action = switch-focus-between-floating-and-tiling;
779 "Mod+Shift+V".action = toggle-window-floating;
780
781 "Mod+Left".action = set-column-width "-10%";
782 "Mod+Down".action = set-window-height "-10%";
783 "Mod+Up".action = set-window-height "+10%";
784 "Mod+Right".action = set-column-width "+10%";
785
786 "Mod+Shift+Z" = {
787 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
788 allow-when-locked = true;
789 };
790 "Mod+Shift+L".action = spawn loginctl "lock-session";
791 "Mod+Shift+E".action = quit;
792 "Mod+Shift+Minus" = {
793 action = spawn systemctl "suspend";
794 allow-when-locked = true;
795 };
796 "Mod+Shift+Control+Minus" = {
797 action = spawn systemctl "hibernate";
798 allow-when-locked = true;
799 };
800 "Mod+Shift+P" = {
801 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
802 allow-when-locked = true;
803 };
804
805 "XF86MonBrightnessUp" = {
806 action = spawn swayosd-client "--brightness" "raise";
807 allow-when-locked = true;
808 };
809 "XF86MonBrightnessDown" = {
810 action = spawn swayosd-client "--brightness" "lower";
811 allow-when-locked = true;
812 };
813 "XF86AudioRaiseVolume" = {
814 action = spawn swayosd-client "--output-volume" "raise";
815 allow-when-locked = true;
816 };
817 "XF86AudioLowerVolume" = {
818 action = spawn swayosd-client "--output-volume" "lower";
819 allow-when-locked = true;
820 };
821 "XF86AudioMute" = {
822 action = spawn swayosd-client "--output-volume" "mute-toggle";
823 allow-when-locked = true;
824 };
825 "XF86AudioMicMute" = {
826 action = spawn swayosd-client "--input-volume" "mute-toggle";
827 allow-when-locked = true;
828 };
829
830 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
831 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
832 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
833 "Mod+Comma".action = spawn makoctl "restore";
834
835 "Mod+Control+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"FocusWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
836 "Mod+Control+Shift+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"MoveColumnToWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
837 }))
838 (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)
839 ]
840 ))
841 ];
842 };
843}