summaryrefslogtreecommitdiff
path: root/accounts/gkleen@sif/niri
diff options
context:
space:
mode:
Diffstat (limited to 'accounts/gkleen@sif/niri')
-rw-r--r--accounts/gkleen@sif/niri/default.nix948
-rw-r--r--accounts/gkleen@sif/niri/mako.nix112
-rw-r--r--accounts/gkleen@sif/niri/swayosd.nix66
-rw-r--r--accounts/gkleen@sif/niri/waybar.nix347
4 files changed, 0 insertions, 1473 deletions
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
deleted file mode 100644
index 0fae56a7..00000000
--- a/accounts/gkleen@sif/niri/default.nix
+++ /dev/null
@@ -1,948 +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
49 with_adjacent_workspace = pkgs.writeShellApplication {
50 name = "with-adjacent-workspace";
51 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
52 text = ''
53 blacklist="$1"
54 shift
55 direction="$1"
56 shift
57 action="$1"
58 shift
59
60 workspaces_json="$(niri msg -j workspaces)"
61 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
62 workspace_output="$(jq -r --arg active_workspace "$active_workspace" '.[] | select(.id == ($active_workspace | tonumber)) | .output' <<<"$workspaces_json")"
63 workspace_idx="$(jq -r '.[] | select(.is_focused) | .idx' <<<"$workspaces_json")"
64
65 jq_script='map(select('
66 case "$direction" in
67 down)
68 # shellcheck disable=SC2016
69 jq_script=''${jq_script}'.idx > ($workspace_idx | tonumber)';;
70 up)
71 # shellcheck disable=SC2016
72 jq_script=''${jq_script}'.idx < ($workspace_idx | tonumber)';;
73 esac
74 # shellcheck disable=SC2016
75 jq_script=''${jq_script}' and .output == $workspace_output and ((.name == null) or (.name | test($blacklist) | not)))) | sort_by(.idx)'
76 [[ $direction == "up" ]] && jq_script=''${jq_script}' | reverse'
77 jq_script=''${jq_script}' | .[0]'
78
79 workspace_json=$(jq -c --arg blacklist "$blacklist" --arg workspace_output "$workspace_output" --arg workspace_idx "$workspace_idx" "$jq_script" <<<"$workspaces_json")
80 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
81 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
82 '';
83 };
84 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^${lib.concatMapStringsSep "|" ({ name, ...}: name) cfg.scratchspaces}$";
85 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
86 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
87
88 with_unnamed_workspace = pkgs.writeShellApplication {
89 name = "with-unnamed-workspace";
90 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
91 text = ''
92 action="$1"
93 shift
94
95 workspaces_json="$(niri msg -j workspaces)"
96 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
97 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
98
99 history_json="$(socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/niri-workspace-history.sock)"
100 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")"
101 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
102 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
103 '';
104 };
105 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
106
107 with_empty_unnamed_workspace = pkgs.writeShellApplication {
108 name = "with-empty-unnamed-workspace";
109 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
110 text = ''
111 action="$1"
112 shift
113
114 workspaces_json="$(niri msg -j workspaces)"
115 active_output="$(jq '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
116 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")"
117 jq --argjson workspace_id "$target_workspace_id" -nc "$action" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
118 '';
119 };
120 with-empty-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_empty_unnamed_workspace);
121
122 with_select_window = pkgs.writeShellApplication {
123 name = "with-select-window";
124 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
125 text = ''
126 window_select="$1"
127 shift
128 action="$1"
129 shift
130
131 windows_json="$(niri msg -j windows)"
132 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
133 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)"
134 # shellcheck disable=SC2016
135 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")"
136
137 [[ -z "$window_json" ]] && exit 1
138
139 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
140 '';
141 };
142 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
143in {
144 imports = [
145 ./waybar.nix
146 ./mako.nix
147 ./swayosd.nix
148 ];
149
150 options = {
151 programs.niri.scratchspaces = lib.mkOption {
152 type = lib.types.listOf (lib.types.submodule ({ config, ... }: {
153 options = {
154 name = lib.mkOption {
155 type = lib.types.str;
156 };
157 match = lib.mkOption {
158 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
159 default = [];
160 };
161 exclude = lib.mkOption {
162 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
163 default = [];
164 };
165 windowRuleExtra = lib.mkOption {
166 type = kdl.types.kdl-nodes;
167 default = [];
168 };
169 key = lib.mkOption {
170 type = lib.types.nullOr lib.types.str;
171 default = null;
172 };
173 moveKey = lib.mkOption {
174 type = lib.types.nullOr lib.types.str;
175 default = let
176 keys = lib.splitString "+" config.key;
177 defMoveKey = lib.concatStringsSep "+" (lib.flatten [
178 (lib.take (lib.length keys - 1) keys)
179 ["Shift"]
180 (lib.takeEnd 1 keys)
181 ]);
182 in if config.key == null then null else defMoveKey;
183 };
184 spawn = lib.mkOption {
185 type = lib.types.nullOr (lib.types.listOf lib.types.str);
186 default = null;
187 };
188 app-id = lib.mkOption {
189 type = lib.types.nullOr lib.types.str;
190 default = null;
191 };
192 selector = lib.mkOption {
193 type = lib.types.nullOr lib.types.str;
194 default = null;
195 };
196 };
197
198 config = lib.mkMerge [
199 (lib.mkIf (config.app-id != null) {
200 match = lib.mkDefault [ { app-id = "^${lib.escapeRegex config.app-id}$"; } ];
201 selector = lib.mkDefault "select(.app_id == \"${config.app-id}\")";
202 })
203 ];
204 }));
205 default = [];
206 };
207 };
208
209 config = {
210 systemd.user.services.xwayland-satellite = {
211 Unit = {
212 BindsTo = [ "graphical-session.target" ];
213 PartOf = [ "graphical-session.target" ];
214 After = [ "graphical-session.target" ];
215 Requisite = [ "graphical-session.target" ];
216 };
217 Service = {
218 Type = "notify";
219 NotifyAccess = "all";
220 Environment = [ "DISPLAY=:0" ];
221 ExecStart = ''${lib.getExe pkgs.xwayland-satellite-unstable} ''${DISPLAY}'';
222 ExecStartPre = "${systemctl} --user import-environment DISPLAY";
223 StandardOutput = "journal";
224 };
225 Install = {
226 WantedBy = [ "graphical-session.target" ];
227 };
228 };
229
230 services.swayidle = {
231 events = [
232 { event = "after-resume"; command = "${lib.getExe niri} msg action power-on-monitors"; }
233 ];
234 timeouts = [
235 { timeout = 540;
236 command = "${lib.getExe niri} msg action power-off-monitors";
237 }
238 ];
239 };
240
241 systemd.user.sockets.niri-workspace-history = {
242 Socket = {
243 ListenStream = "%t/niri-workspace-history.sock";
244 SocketMode = "0600";
245 };
246 };
247 systemd.user.services.niri-workspace-history = {
248 Unit = {
249 BindsTo = [ "niri.service" ];
250 After = [ "niri.service" ];
251 };
252 Install = {
253 WantedBy = [ "niri.service" ];
254 };
255 Service = {
256 Type = "simple";
257 Sockets = [ "niri-workspace-history.socket" ];
258 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" { flakeIgnore = ["E501"]; } ''
259 import os
260 import socket
261 import json
262 # import sys
263 from collections import defaultdict
264 from threading import Thread, Lock
265 from socketserver import StreamRequestHandler, ThreadingTCPServer
266 from contextlib import contextmanager
267 from io import TextIOWrapper
268
269
270 @contextmanager
271 def detaching(thing):
272 try:
273 yield thing
274 finally:
275 thing.detach()
276
277
278 workspace_history = defaultdict(list)
279 history_lock = Lock()
280
281
282 def monitor_niri():
283 workspaces = list()
284
285 def focus_workspace(output, workspace):
286 with history_lock:
287 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace]
288 # print(json.dumps(workspace_history), file=sys.stderr)
289
290 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
291 sock.connect(os.environ["NIRI_SOCKET"])
292 sock.send(b"\"EventStream\"\n")
293 for line in sock.makefile(buffering=1, encoding='utf-8'):
294 if line_json := json.loads(line):
295 if "WorkspacesChanged" in line_json:
296 workspaces = line_json["WorkspacesChanged"]["workspaces"]
297 for ws in workspaces:
298 if ws["is_focused"]:
299 focus_workspace(ws["output"], ws["id"])
300 if "WorkspaceActivated" in line_json:
301 for ws in workspaces:
302 if ws["id"] != line_json["WorkspaceActivated"]["id"]:
303 continue
304 focus_workspace(ws["output"], ws["id"])
305 break
306
307
308 class RequestHandler(StreamRequestHandler):
309 def handle(self):
310 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out:
311 with history_lock:
312 json.dump(workspace_history, out)
313
314
315 class Server(ThreadingTCPServer):
316 def __init__(self):
317 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False)
318 self.socket = socket.fromfd(3, self.address_family, self.socket_type)
319
320
321 def run_server():
322 with Server() as server:
323 server.serve_forever()
324
325
326 niri = Thread(target=monitor_niri)
327 niri.daemon = True
328 niri.start()
329
330 server_thread = Thread(target=run_server)
331 server_thread.daemon = True
332 server_thread.start()
333
334 while True:
335 server_thread.join(timeout=0.5)
336 niri.join(timeout=0.5)
337
338 if not (niri.is_alive() and server_thread.is_alive()):
339 break
340 '';
341 };
342 };
343 systemd.user.services.niri-workspace-sort = {
344 Unit = {
345 BindsTo = [ "niri.service" ];
346 After = [ "niri.service" ];
347 };
348 Install = {
349 WantedBy = [ "niri.service" ];
350 };
351 Service = {
352 Type = "simple";
353 ExecStart = pkgs.writers.writePython3 "niri-workspace-sort" { flakeIgnore = ["E501"]; } ''
354 import os
355 import sys
356 import socket
357 import json
358
359 outputs = None
360 only = {'HDMI-A-1': {'bmr'}, 'eDP-1': {'vid'}}
361
362
363 class Niri(socket.socket):
364 def __init__(self):
365 super().__init__(socket.AF_UNIX, socket.SOCK_STREAM)
366 super().connect(os.environ["NIRI_SOCKET"])
367 self.fh = super().makefile(mode='rw', buffering=1, encoding='utf-8')
368
369 def cmd(self, obj):
370 print(json.dumps(obj, separators=(',', ':')), flush=True, file=self.fh)
371
372 def event_stream(self):
373 self.cmd("EventStream")
374 return self.fh
375
376
377 with Niri() as niri, Niri().event_stream() as niri_stream:
378 for line in niri_stream:
379 workspaces = None
380 if line_json := json.loads(line):
381 if "WorkspacesChanged" in line_json:
382 workspaces = line_json["WorkspacesChanged"]["workspaces"]
383
384 if workspaces is None:
385 continue
386
387 old_outputs = outputs
388 outputs = {ws["output"] for ws in workspaces}
389 if old_outputs is None:
390 print("Initial outputs: {}".format(outputs), file=sys.stderr)
391 continue
392
393 new_outputs = outputs - old_outputs
394 if not new_outputs:
395 continue
396 print("New outputs: {}".format(new_outputs), file=sys.stderr)
397
398 relevant_workspaces = list(filter(lambda ws: (ws["name"] is not None) or (ws["active_window_id"] is not None), workspaces))
399 target_output = next(iter(outputs - set(only.keys())))
400 if not target_output:
401 continue
402 for ws in relevant_workspaces:
403 ws_ident = ws["name"] if ws["name"] is not None else (ws["output"], ws["idx"])
404 if ws["output"] not in set(only.keys()):
405 continue
406 if ws_ident in only[ws["output"]]:
407 continue
408
409 print("{} -> {}".format(ws_ident, target_output), file=sys.stderr)
410 niri.cmd({"Action": {"MoveWorkspaceToMonitor": {"reference": {"Id": ws["id"]}, "output": target_output}}})
411 '';
412 Restart = "on-failure";
413 RestartSec = 10;
414 };
415 };
416
417 programs.niri.scratchspaces = [
418 { name = "pwctl";
419 key = "Mod+Control+A";
420 spawn = ["pwvucontrol"];
421 app-id = "com.saivert.pwvucontrol";
422 }
423 { name = "kpxc";
424 exclude = [
425 { title = "^Unlock Database.*"; }
426 { title = "^Access Request.*"; }
427 { title = ".*Passkey credentials$"; }
428 ];
429 windowRuleExtra = [
430 (kdl.leaf "open-focused" false)
431 ];
432 key = "Mod+Control+P";
433 app-id = "org.keepassxc.KeePassXC";
434 spawn = [ "keepassxc" ];
435 }
436 { name = "bmgr";
437 key = "Mod+Control+B";
438 app-id = ".blueman-manager-wrapped";
439 spawn = [ "blueman-manager" ];
440 }
441 { name = "term";
442 key = "Mod+Control+Return";
443 app-id = "kitty-scratch";
444 spawn = [ "kitty" "--app-id" "kitty-scratch" ];
445 }
446 { name = "edit";
447 match = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
448 key = "Mod+Control+E";
449 selector = "select(.app_id == \"emacs\" and .title == \"scratch\")";
450 spawn = [ "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))" ];
451 }
452 { name = "eff";
453 key = "Mod+Control+O";
454 app-id = "com.github.wwmm.easyeffects";
455 spawn = [ "easyeffects" ];
456 }
457 ];
458 programs.niri.config =
459 let
460 inherit (kdl) node plain leaf flag;
461 optional-node = cond: v:
462 if cond
463 then v
464 else null;
465 opt-props = lib.filterAttrs (lib.const (value: value != null));
466 in
467 [ (flag "prefer-no-csd")
468
469 (leaf "screenshot-path" "~/screenshots/%Y-%m-%dT%H:%M:%S.png")
470
471 (plain "hotkey-overlay" [
472 (flag "skip-at-startup")
473 ])
474
475 (plain "input" [
476 (plain "keyboard" [
477 (leaf "repeat-delay" 300)
478 (leaf "repeat-rate" 50)
479
480 (plain "xkb" [
481 (leaf "layout" "us,us")
482 (leaf "variant" "dvp,")
483 (leaf "options" "compose:caps,grp:win_space_toggle")
484 ])
485 ])
486
487 (flag "workspace-auto-back-and-forth")
488 # (leaf "focus-follows-mouse" {})
489 # (flag "warp-mouse-to-focus")
490
491 # (plain "touchpad" [ (flag "off") ])
492 (plain "trackball" [
493 (leaf "scroll-method" "on-button-down")
494 (leaf "scroll-button" 278)
495 ])
496 (plain "touch" [
497 (leaf "map-to-output" "eDP-1")
498 ])
499 ])
500
501 (plain "gestures" [
502 (plain "hot-corners" [(flag "off")])
503 ])
504
505 (plain "environment" (lib.mapAttrsToList leaf {
506 NIXOS_OZONE_WL = "1";
507 QT_QPA_PLATFORM = "wayland";
508 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
509 GDK_BACKEND = "wayland";
510 SDL_VIDEODRIVER = "wayland";
511 DISPLAY = ":0";
512 ELECTRON_OZONE_PLATFORM_HINT = "auto";
513 }))
514
515 (node "output" "eDP-1" [
516 (leaf "scale" 1.5)
517 (leaf "position" { x = 0; y = 0; })
518 ])
519 (node "output" "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" [
520 (leaf "scale" 1.5)
521 (leaf "position" { x = 2560; y = 0; })
522 ])
523 (node "output" "HP Inc. HP 727pu CN4417143K" [
524 (leaf "mode" "2560x1440@119.998")
525 (leaf "scale" 1)
526 (leaf "position" { x = 2560; y = 0; })
527 (flag "variable-refresh-rate")
528 ])
529
530 (plain "debug" [
531 (leaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render")
532 ])
533
534 (plain "animations" [
535 (leaf "slowdown" 0.5)
536 (plain "workspace-switch" [(flag "off")])
537 ])
538
539 (plain "layout" [
540 (leaf "gaps" 8)
541 (plain "struts" [
542 (leaf "left" 26)
543 (leaf "right" 26)
544 (leaf "top" 0)
545 (leaf "bottom" 0)
546 ])
547 (plain "border" [
548 (leaf "width" 2)
549 (leaf "active-gradient" {
550 from = "hsla(195 100% 45% 1)";
551 to = "hsla(155 100% 37.5% 1)";
552 angle = 29;
553 relative-to = "workspace-view";
554 })
555 (leaf "inactive-gradient" {
556 from = "hsla(0 0% 27.7% 1)";
557 to = "hsla(0 0% 23% 1)";
558 angle = 29;
559 relative-to = "workspace-view";
560 })
561 ])
562 (plain "focus-ring" [
563 (flag "off")
564 ])
565
566 (plain "preset-column-widths" (map (prop: leaf "proportion" prop) [
567 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.)
568 ]))
569 (plain "default-column-width" [ (leaf "proportion" (1. / 2.)) ])
570 (plain "preset-window-heights" (map (prop: leaf "proportion" prop) [
571 (1. / 3.) (1. / 2.) (2. / 3.) (1.)
572 ]))
573
574 (flag "always-center-single-column")
575
576 (plain "tab-indicator" [
577 (leaf "gap" 4)
578 (leaf "width" 8)
579 (leaf "gaps-between-tabs" 4)
580 (flag "place-within-column")
581 (leaf "length" { total-proportion = 1.; })
582 (leaf "active-gradient" {
583 from = "hsla(195 100% 60% 0.75)";
584 to = "hsla(155 100% 50% 0.75)";
585 angle = 29;
586 relative-to = "workspace-view";
587 })
588 (leaf "inactive-gradient" {
589 from = "hsla(0 0% 42% 0.66)";
590 to = "hsla(0 0% 35% 0.66)";
591 angle = 29;
592 relative-to = "workspace-view";
593 })
594 ])
595 ])
596
597 (plain "cursor" [
598 (flag "hide-when-typing")
599 ])
600
601 (map (name:
602 (node "workspace" name [
603 (leaf "open-on-output" "eDP-1")
604 ])
605 ) (map ({name, ...}: name) cfg.scratchspaces))
606 (map (name:
607 (leaf "workspace" name)
608 ) ["comm" "web" "vid" "bmr"])
609
610 (plain "window-rule" [
611 (leaf "clip-to-geometry" true)
612 ])
613
614 (plain "window-rule" [
615 (leaf "match" { is-floating = true; })
616 (leaf "geometry-corner-radius" 8)
617 (plain "shadow" [ (flag "on") ])
618 ])
619
620 (plain "window-rule" [
621 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; })
622 (leaf "block-out-from" "screencast")
623 ])
624 (plain "window-rule" [
625 (map (title:
626 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; })
627 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$"])
628 (leaf "open-focused" true)
629 (leaf "open-floating" true)
630 ])
631
632 (map ({ name, match, exclude, windowRuleExtra, ... }:
633 (optional-node (match != []) (plain "window-rule" [
634 (map (leaf "match") match)
635 (map (leaf "exclude") exclude)
636 (leaf "open-on-workspace" name)
637 (leaf "open-maximized" true)
638 windowRuleExtra
639 ]))
640 ) cfg.scratchspaces)
641
642 (plain "window-rule" [
643 (leaf "match" { app-id = "^emacs$"; })
644 (leaf "match" { app-id = "^firefox$"; })
645 (plain "default-column-width" [(leaf "proportion" (2. / 3.))])
646 ])
647 (plain "window-rule" [
648 (leaf "match" { app-id = "^kitty$"; })
649 (leaf "match" { app-id = "^kitty-play$"; })
650 (plain "default-column-width" [(leaf "proportion" (1. / 3.))])
651 ])
652
653 (plain "window-rule" [
654 (leaf "match" { app-id = "^thunderbird$"; })
655 (leaf "match" { app-id = "^Element$"; })
656 (leaf "match" { app-id = "^Rainbow$"; })
657 (leaf "open-on-workspace" "comm")
658 ])
659 (plain "window-rule" [
660 (leaf "match" { app-id = "^firefox$"; })
661 (leaf "open-on-workspace" "web")
662 (leaf "open-maximized" true)
663 ])
664 (plain "window-rule" [
665 (leaf "match" { app-id = "^mpv$"; })
666 (leaf "open-on-workspace" "vid")
667 (plain "default-column-width" [(leaf "proportion" 1.)])
668 ])
669 (plain "window-rule" [
670 (leaf "match" { app-id = "^kitty-play$"; })
671 (leaf "open-on-workspace" "vid")
672 (leaf "open-focused" false)
673 ])
674 (plain "window-rule" [
675 (leaf "match" { app-id = "^pdfpc$"; })
676 (plain "default-column-width" [(leaf "proportion" 1.)])
677 ])
678 (plain "window-rule" [
679 (leaf "match" { app-id = "^pdfpc$"; title = "^.*presentation.*$"; })
680 (plain "default-column-width" [(leaf "proportion" 1.)])
681 (leaf "open-fullscreen" true)
682 (leaf "open-on-workspace" "bmr")
683 (leaf "open-focused" false)
684 ])
685 (plain "window-rule" [
686 (map (leaf "match") [
687 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
688 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; }
689 { app-id = "^xdg-desktop-portal-gtk$"; }
690 ])
691 (leaf "open-floating" true)
692 ])
693 (plain "window-rule" [
694 (leaf "match" { app-id = "^org\\.pwmt\\.zathura$"; })
695 (leaf "match" { app-id = "^evince$"; })
696 (leaf "match" { app-id = "^org\\.gnome\\.Papers$"; })
697 (leaf "default-column-display" "tabbed")
698 ])
699
700 (plain "layer-rule" [
701 (leaf "match" { namespace = "^notifications$"; })
702 (leaf "match" { namespace = "^waybar$"; })
703 (leaf "match" { namespace = "^launcher$"; })
704 (leaf "block-out-from" "screencast")
705 ])
706
707 (plain "binds"
708 (let
709 bind = name: cfg: node name (opt-props {
710 cooldown-ms = cfg.cooldown-ms or null;
711 }
712 // (lib.optionalAttrs (!(cfg.repeat or true)) {
713 repeat = false;
714 })
715 // (lib.optionalAttrs (cfg.allow-when-locked or false) {
716 allow-when-locked = true;
717 })) (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
718 in
719 [
720 (lib.mapAttrsToList bind (with config.lib.niri.actions; {
721 "Mod+Slash".action = show-hotkey-overlay;
722
723 "Mod+Return".action = spawn terminal;
724 "Mod+Shift+Return".action =
725 let
726 nushellKitty = pkgs.symlinkJoin {
727 name = "nushell-kitty";
728 paths = [ config.programs.kitty.package ];
729 buildInputs = [ pkgs.makeWrapper ];
730 postBuild = ''
731 wrapProgram $out/bin/kitty \
732 --add-flags "--config ${pkgs.writeText "kitty.conf" ''
733 include $HOME/${config.xdg.configFile."kitty/kitty.conf".target}
734 shell ${lib.getExe config.programs.nushell.package}
735 ''}"
736 '';
737 };
738 in spawn (lib.getExe' nushellKitty "kitty");
739 "Mod+Q".action = close-window;
740 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
741 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
742
743 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
744 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
745 name = "queue-yt-dlp";
746 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
747 text = ''
748 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
749 '';
750 }));
751 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
752 name = "queue-yt-dlp";
753 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
754 text = ''
755 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
756 '';
757 }));
758 "Mod+Alt+M".action = spawn (lib.getExe' pkgs.screen-message "sm") "-n" "Fira Mono" "-a" "1" "-f" "#fff" "-b" "#000";
759
760 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
761 name = "qalc-fuzzel";
762 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
763 text = ''
764 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
765 prev() {
766 FOUND=false
767 while IFS= read -r line; do
768 [[ -n "$line" ]] || continue
769 FOUND=true
770 echo "$line"
771 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)
772 $FOUND || echo
773 }
774 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
775 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
776 QALC_RES="$FUZZEL_RES"
777 QALC_RET=0
778 else
779 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
780 QALC_RET=$?
781 fi
782 [[ -n "$QALC_RES" ]] || exit 1
783 EXISTING=false
784 set +o pipefail
785 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
786 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
787 set -o pipefail
788 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
789 set +o pipefail
790 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
791 set -o pipefail
792 cat >"$RES_FILE" <<<"$QALC_RES"
793 fi
794 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
795 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
796 notify-send "$QALC_RES"
797 '';
798 }));
799 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
800 name = "emoji-fuzzel";
801 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
802 text = ''
803 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
804 [[ -n "$FUZZEL_RES" ]] || exit 1
805 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
806 '';
807 }));
808 "Print".action = screenshot;
809 "Control+Print".action = screenshot-window;
810 "Shift+Print".action = kdl.magic-leaf "screenshot-screen";
811 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
812 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
813
814 "Mod+Escape" = {
815 allow-inhibiting = false;
816 action = toggle-keyboard-shortcuts-inhibit;
817 };
818
819 "Mod+H".action = focus-column-left;
820 "Mod+T".action = focus-window-down;
821 "Mod+N".action = focus-window-up;
822 "Mod+S".action = focus-column-right;
823
824 "Mod+Shift+H".action = move-column-left;
825 "Mod+Shift+T".action = move-window-down;
826 "Mod+Shift+N".action = move-window-up;
827 "Mod+Shift+S".action = move-column-right;
828
829 "Mod+Control+H".action = focus-monitor-left;
830 "Mod+Control+T".action = focus-monitor-down;
831 "Mod+Control+N".action = focus-monitor-up;
832 "Mod+Control+S".action = focus-monitor-right;
833
834 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
835 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
836 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
837 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
838
839 "Mod+G".action = focus-adjacent-workspace "down";
840 "Mod+C".action = focus-adjacent-workspace "up";
841
842 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
843 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
844
845 "Mod+Shift+Control+G".action = move-workspace-down;
846 "Mod+Shift+Control+C".action = move-workspace-up;
847
848 "Mod+ParenLeft".action = focus-workspace "comm";
849 "Mod+Shift+ParenLeft".action = kdl.magic-leaf "move-column-to-workspace" "comm";
850
851 "Mod+ParenRight".action = focus-workspace "web";
852 "Mod+Shift+ParenRight".action = kdl.magic-leaf "move-column-to-workspace" "web";
853
854 "Mod+BraceRight".action = focus-workspace "read";
855 "Mod+Shift+BraceRight".action = kdl.magic-leaf "move-column-to-workspace" "read";
856
857 "Mod+BraceLeft".action = focus-workspace "mon";
858 "Mod+Shift+BraceLeft".action = kdl.magic-leaf "move-column-to-workspace" "mon";
859
860 "Mod+Asterisk".action = focus-workspace "vid";
861 "Mod+Shift+Asterisk".action = kdl.magic-leaf "move-column-to-workspace" "vid";
862
863 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
864 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
865
866 "Mod+M".action = consume-or-expel-window-left;
867 "Mod+W".action = consume-or-expel-window-right;
868
869 "Mod+Shift+M".action = toggle-column-tabbed-display;
870
871 "Mod+R".action = switch-preset-column-width;
872 "Mod+Shift+R".action = maximize-column;
873 "Mod+Shift+Ctrl+R".action = switch-preset-window-height;
874 "Mod+F".action = center-column;
875 "Mod+Shift+F".action = toggle-windowed-fullscreen;
876 "Mod+Ctrl+Shift+F".action = fullscreen-window;
877
878 "Mod+V".action = switch-focus-between-floating-and-tiling;
879 "Mod+Shift+V".action = toggle-window-floating;
880
881 "Mod+Left".action = set-column-width "-10%";
882 "Mod+Down".action = set-window-height "-10%";
883 "Mod+Up".action = set-window-height "+10%";
884 "Mod+Right".action = set-column-width "+10%";
885
886 "Mod+Shift+Z" = {
887 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
888 allow-when-locked = true;
889 };
890 "Mod+Shift+L".action = spawn loginctl "lock-session";
891 "Mod+Shift+E".action = quit;
892 "Mod+Shift+Minus" = {
893 action = spawn systemctl "suspend";
894 allow-when-locked = true;
895 };
896 "Mod+Shift+Control+Minus" = {
897 action = spawn systemctl "hibernate";
898 allow-when-locked = true;
899 };
900 "Mod+Shift+P" = {
901 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
902 allow-when-locked = true;
903 };
904
905 "XF86MonBrightnessUp" = {
906 action = spawn swayosd-client "--brightness" "raise";
907 allow-when-locked = true;
908 };
909 "XF86MonBrightnessDown" = {
910 action = spawn swayosd-client "--brightness" "lower";
911 allow-when-locked = true;
912 };
913 "XF86AudioRaiseVolume" = {
914 action = spawn swayosd-client "--output-volume" "raise";
915 allow-when-locked = true;
916 };
917 "XF86AudioLowerVolume" = {
918 action = spawn swayosd-client "--output-volume" "lower";
919 allow-when-locked = true;
920 };
921 "XF86AudioMute" = {
922 action = spawn swayosd-client "--output-volume" "mute-toggle";
923 allow-when-locked = true;
924 };
925 "XF86AudioMicMute" = {
926 action = spawn swayosd-client "--input-volume" "mute-toggle";
927 allow-when-locked = true;
928 };
929
930 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
931 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
932 "Mod+Period".action = spawn makoctl "menu" "--" (lib.getExe config.programs.fuzzel.package) "--dmenu";
933 "Mod+Comma".action = spawn makoctl "restore";
934
935 "Mod+Control+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"FocusWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
936 "Mod+Control+Shift+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"MoveColumnToWorkspace\":{\"reference\":{\"Id\": $workspace_id}, \"focus\": true}}}";
937
938 "Mod+X".action = set-dynamic-cast-window;
939 "Mod+Shift+X".action = set-dynamic-cast-monitor;
940 "Mod+Control+Shift+X".action = clear-dynamic-cast-target;
941 }))
942 (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)
943 (map ({ name, moveKey, ...}: if moveKey != null then bind moveKey { action = kdl.magic-leaf "move-column-to-workspace" name; } else null) cfg.scratchspaces)
944 ]
945 ))
946 ];
947 };
948}
diff --git a/accounts/gkleen@sif/niri/mako.nix b/accounts/gkleen@sif/niri/mako.nix
deleted file mode 100644
index 810bff89..00000000
--- a/accounts/gkleen@sif/niri/mako.nix
+++ /dev/null
@@ -1,112 +0,0 @@
1{ config, lib, pkgs, ... }:
2{
3 config = {
4 services.mako = {
5 enable = true;
6 settings = {
7 font = "Fira Sans 10";
8 format = "<i>%s</i>\\n%b";
9 margin = "2";
10 max-visible = -1;
11 background-color = "#000000dd";
12 progress-color = "source #223544ff";
13 width = 384;
14 outer-margin = 1;
15 max-history = 100;
16 max-icon-size = 48;
17 };
18 criteria = {
19 grouped.format = "<b>(%g)</b> <i>%s</i>\\n%b";
20 "urgency=low".text-color = "#999999ff";
21 "urgency=critical".background-color = "#900000dd";
22 "app-name=Element".group-by = "summary";
23 "app-name=poweralertd" = {
24 history = false;
25 ignore-timeout = true;
26 default-timeout = 2000;
27 };
28 "mode=silent".invisible = true;
29 };
30 package = pkgs.symlinkJoin {
31 name = "${pkgs.mako.name}-wrapped";
32 paths = with pkgs; [ mako ];
33 inherit (pkgs.mako) meta;
34 postBuild = ''
35 rm -r $out/share/dbus-1
36 '';
37 };
38 };
39 systemd.user.services.mako = {
40 Unit = {
41 Description = "Mako notification daemon";
42 PartOf = [ "graphical-session.target" ];
43 };
44 Install = {
45 WantedBy = [ "graphical-session.target" ];
46 };
47 Service = {
48 Type = "dbus";
49 BusName = "org.freedesktop.Notifications";
50 ExecStart = lib.getExe config.services.mako.package;
51 RestartSec = 5;
52 Restart = "always";
53 };
54 };
55
56 systemd.user.services.mako-follows-focus = {
57 Unit = {
58 BindsTo = [ "niri.service" "mako.service" ];
59 After = [ "niri.service" "mako.service" ];
60 };
61 Service = {
62 Type = "simple";
63 Restart = "always";
64 ExecStart = pkgs.writers.writePython3 "mako-follows-focus" {
65 libraries = with pkgs.python3Packages; [];
66 } ''
67 import os
68 import socket
69 import json
70 import subprocess
71
72
73 current_output = None
74 workspaces = []
75
76
77 def output_changed(new_output):
78 global current_output
79
80 if current_output == new_output:
81 return
82
83 current_output = new_output
84 subprocess.run(["makoctl", "reload"])
85
86
87 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
88 sock.connect(os.environ["NIRI_SOCKET"])
89 sock.send(b"\"EventStream\"\n")
90 for line in sock.makefile(buffering=1, encoding='utf-8'):
91 if line_json := json.loads(line):
92 if "WorkspacesChanged" in line_json:
93 workspaces = line_json["WorkspacesChanged"]["workspaces"]
94 for workspace in workspaces:
95 if not workspace["is_focused"]:
96 continue
97 output_changed(workspace["output"])
98 break
99 if "WorkspaceActivated" in line_json and line_json["WorkspaceActivated"]["focused"]: # noqa: E501
100 for workspace in workspaces:
101 if not workspace["id"] == line_json["WorkspaceActivated"]["id"]: # noqa: E501
102 continue
103 output_changed(workspace["output"])
104 break
105 '';
106 };
107 Install = {
108 WantedBy = [ "mako.service" ];
109 };
110 };
111 };
112}
diff --git a/accounts/gkleen@sif/niri/swayosd.nix b/accounts/gkleen@sif/niri/swayosd.nix
deleted file mode 100644
index 54ebb302..00000000
--- a/accounts/gkleen@sif/niri/swayosd.nix
+++ /dev/null
@@ -1,66 +0,0 @@
1{ pkgs, ... }:
2{
3 config = {
4 services.swayosd = {
5 enable = true;
6 topMargin = 0.4769706078;
7 stylePath = pkgs.runCommand "style.css" {
8 passAsFile = [ "src" ];
9 src = ''
10 window#osd {
11 padding: 12px 20px;
12 border-radius: 999px;
13 border: none;
14 background: rgba(0, 0, 0, 0.87);
15
16 #container {
17 margin: 16px;
18 }
19
20 image,
21 label {
22 color: rgb(255, 255, 255);
23
24 &:disabled {
25 opacity: 1;
26 color: rgb(84, 84, 84);
27 }
28 }
29
30 progressbar {
31 min-height: 6px;
32 border-radius: 999px;
33 background: transparent;
34 border: none;
35
36 trough, progress {
37 min-height: inherit;
38 border-radius: inherit;
39 border: none;
40 }
41
42 trough {
43 background: rgb(127, 127, 127);
44 }
45 progress {
46 background: rgb(255, 255, 255);
47 }
48
49 &:disabled {
50 opacity: 1;
51
52 trough {
53 background: rgb(19, 19, 19);
54 }
55 progress {
56 background: rgb(38, 38, 38);
57 }
58 }
59 }
60 }
61 '';
62 buildInputs = with pkgs; [sass];
63 } "scss -C --sourcemap=none --style=compact $srcPath $out";
64 };
65 };
66}
diff --git a/accounts/gkleen@sif/niri/waybar.nix b/accounts/gkleen@sif/niri/waybar.nix
deleted file mode 100644
index cc131c08..00000000
--- a/accounts/gkleen@sif/niri/waybar.nix
+++ /dev/null
@@ -1,347 +0,0 @@
1{ lib, config, pkgs, ... }:
2let
3 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
4in {
5 config = {
6 programs.waybar = {
7 enable = true;
8 systemd = {
9 enable = true;
10 target = "graphical-session.target";
11 };
12 settings = let
13 windowRewrites = {
14 "(.*) — Mozilla Firefox" = "$1";
15 "(.*) - Mozilla Thunderbird" = "$1";
16 "(.*) - mpv" = "$1";
17 };
18 iconSize = 11;
19 in [
20 {
21 layer = "top";
22 position = "top";
23 height = 21;
24 output = [ "eDP-1" "DP-2" "DP-3" ];
25 modules-left = [ "niri/workspaces" ];
26 modules-center = [ "niri/window" ];
27 modules-right = [ "custom/worktime" "custom/worktime-today"
28 "custom/weather"
29 "custom/keymap"
30 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "clock" ];
31
32 "custom/mako" = {
33 format = "{}";
34 return-type = "json";
35 exec = pkgs.writers.writePython3 "mako-silent" { libraries = [ pkgs.python3Packages.dbus-next ]; } ''
36 from dbus_next.aio import MessageBus
37
38 import asyncio
39
40 import json
41
42
43 loop = asyncio.new_event_loop()
44 asyncio.set_event_loop(loop)
45
46
47 async def main():
48 bus = await MessageBus().connect()
49 # the introspection xml would normally be included in your project, but
50 # this is convenient for development
51 introspection = await bus.introspect('org.freedesktop.Notifications', '/fr/emersion/Mako') # noqa: E501
52
53 obj = bus.get_proxy_object('org.freedesktop.Notifications', '/fr/emersion/Mako', introspection) # noqa: E501
54 mako = obj.get_interface('fr.emersion.Mako')
55 properties = obj.get_interface('org.freedesktop.DBus.Properties')
56
57 async def print_mode():
58 modes = await mako.get_modes()
59 is_silent = "silent" in modes
60 icon = "&#xf009b;" if is_silent else "&#xf009a;"
61 text = f"<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>" # noqa: E501
62 if is_silent:
63 text = f"<span color=\"#ffffff\">{text}</span>"
64 print(json.dumps({'text': text, 'tooltip': ', '.join(modes)}, separators=(',', ':')), flush=True) # noqa: E501
65
66 async def on_properties_changed(interface_name, changed_properties, invalidated_properties): # noqa: E501
67 if "Modes" not in invalidated_properties:
68 return
69
70 await print_mode()
71
72 properties.on_properties_changed(on_properties_changed)
73 await print_mode()
74
75 await loop.create_future()
76
77
78 loop.run_until_complete(main())
79 '';
80 on-click = "makoctl mode -t silent";
81 };
82 "custom/weather" = {
83 format = "{}";
84 tooltip = true;
85 interval = 3600;
86 exec = "${lib.getExe pkgs.wttrbar} --hide-conditions --nerd --custom-indicator \"<span font=\\\"Symbols Nerd Font Mono\\\" size=\\\"100%\\\">{ICON}</span> {FeelsLikeC}°\"";
87 return-type = "json";
88 };
89 "custom/keymap" = {
90 format = "{}";
91 tooltip = true;
92 return-type = "json";
93 exec = pkgs.writers.writePython3 "keymap" {} ''
94 import os
95 import socket
96 import json
97
98
99 def output(keymap):
100 short = keymap
101 if keymap == "English (programmer Dvorak)":
102 short = "dvp"
103 elif keymap == "English (US)":
104 short = "<span color=\"#ffffff\">us</span>"
105 print(json.dumps({'text': short, 'tooltip': keymap}, separators=(',', ':')), flush=True) # noqa: E501
106
107
108 keyboard_layouts = []
109
110 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
111 sock.connect(os.environ["NIRI_SOCKET"])
112 sock.send(b"\"EventStream\"\n")
113 for line in sock.makefile(buffering=1, encoding='utf-8'):
114 if line_json := json.loads(line):
115 if "KeyboardLayoutsChanged" in line_json:
116 keyboard_layouts = line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["names"] # noqa: E501
117 output(keyboard_layouts[line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["current_idx"]]) # noqa: E501
118 if "KeyboardLayoutSwitched" in line_json:
119 output(keyboard_layouts[line_json["KeyboardLayoutSwitched"]["idx"]]) # noqa: E501
120 '';
121 on-click = "niri msg action switch-layout next";
122 };
123 "custom/worktime" = {
124 interval = 60;
125 exec = "${lib.getExe pkgs.worktime} time --waybar";
126 return-type = "json";
127 };
128 "custom/worktime-today" = {
129 interval = 60;
130 exec = "${lib.getExe pkgs.worktime} today --waybar";
131 return-type = "json";
132 };
133 "niri/workspaces" = {
134 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
135 };
136 "niri/window" = {
137 separate-outputs = true;
138 icon = true;
139 icon-size = 14;
140 rewrite = windowRewrites;
141 };
142 clock = {
143 interval = 1;
144 # timezone = "Europe/Berlin";
145 format = "W{:%V-%u %F %H:%M:%S%Ez}";
146 tooltip-format = "<tt><small>{calendar}</small></tt>";
147 calendar = {
148 mode = "year";
149 mode-mon-col = 3;
150 weeks-pos = "left";
151 on-scroll = 1;
152 format = {
153 months = "<span color='#ffead3'><b>{}</b></span>";
154 days = "{}";
155 weeks = "<span color='#99ffdd'><b>{}</b></span>";
156 weekdays = "<span color='#ffcc66'><b>{}</b></span>";
157 today = "<span color='#ff6699'><b>{}</b></span>";
158 };
159 };
160 };
161 battery = {
162 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
163 icon-size = iconSize - 2;
164 states = { warning = 30; critical = 15; };
165 format-icons = ["&#xf008e;" "&#xf007a;" "&#xf007b;" "&#xf007c;" "&#xf007d;" "&#xf007e;" "&#xf007f;" "&#xf0080;" "&#xf0081;" "&#xf0082;" "&#xf0079;" ];
166 format-charging = "&#xf0084;";
167 format-plugged = "&#xf06a5;";
168 tooltip-format = "{capacity}% {timeTo}";
169 interval = 20;
170 };
171 tray = {
172 icon-size = 16;
173 # show-passive-items = true;
174 spacing = 1;
175 };
176 privacy = {
177 icon-spacing = 7;
178 icon-size = iconSize;
179 modules = [
180 { type = "screenshare"; }
181 { type = "audio-in"; }
182 ];
183 };
184 idle_inhibitor = {
185 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
186 icon-size = iconSize;
187 format-icons = { activated = "&#xf0208;"; deactivated = "&#xf0209;"; };
188 timeout = 120;
189 };
190 backlight = {
191 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
192 icon-size = iconSize;
193 tooltip-format = "{percent}%";
194 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"];
195 on-scroll-up = "${swayosd-client} --brightness raise";
196 on-scroll-down = "${swayosd-client} --brightness lower";
197 };
198 wireplumber = {
199 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
200 icon-size = iconSize;
201 tooltip-format = "{volume}% {node_name}";
202 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"];
203 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>";
204 # ignored-sinks = ["Easy Effects Sink"];
205 on-scroll-up = "${swayosd-client} --output-volume raise";
206 on-scroll-down = "${swayosd-client} --output-volume lower";
207 on-click = "${swayosd-client} --output-volume mute-toggle";
208 };
209 }
210 {
211 layer = "top";
212 position = "top";
213 height = 14;
214 output = [ "!eDP-1" "!DP-2" "!DP-3" ];
215 modules-left = [ "niri/workspaces" ];
216 modules-center = [ "niri/window" ];
217 modules-right = [ "clock" ];
218
219 "niri/workspaces" = {
220 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
221 };
222 "niri/window" = {
223 separate-outputs = true;
224 icon = true;
225 icon-size = 14;
226 rewrite = windowRewrites;
227 };
228 clock = {
229 interval = 1;
230 # timezone = "Europe/Berlin";
231 format = "{:%H:%M}";
232 tooltip-format = "W{:%V-%u %F %H:%M:%S%Ez}";
233 };
234 }
235 ];
236 style = ''
237 @define-color white #ffffff;
238 @define-color grey #555555;
239 @define-color blue #1a8fff;
240 @define-color green #23fd00;
241 @define-color orange #f28a21;
242 @define-color red #f2201f;
243
244 * {
245 border: none;
246 font-family: "Fira Sans";
247 font-size: 10pt;
248 min-height: 0;
249 }
250
251 window#waybar {
252 background-color: rgba(0, 0, 0, 0.66);
253 color: @white;
254 }
255
256 .modules-left {
257 margin-left: 38px;
258 }
259 .modules-right {
260 margin-right: 38px;
261 }
262
263 .module {
264 margin: 0 5px;
265 }
266
267 #workspaces button {
268 color: @white;
269 padding: 2px 5px;
270 }
271 #workspaces button.empty {
272 color: @grey;
273 }
274 #workspaces button.active {
275 color: @green;
276 }
277 #workspaces button.urgent {
278 color: @red;
279 }
280
281 #custom-weather, #custom-keymap, #custom-worktime, #custom-worktime-today {
282 color: @grey;
283 margin: 0 5px;
284 }
285 #custom-weather {
286 margin-right: 3px;
287 }
288 #custom-keymap {
289 margin-left: 3px;
290 margin-right: 3px;
291 }
292
293 #tray {
294 margin: 0;
295 }
296 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako {
297 color: @grey;
298 margin: 0 5px 0 2px;
299 }
300 #idle_inhibitor {
301 margin-right: 4px;
302 margin-left: 6px;
303 }
304 #custom-mako {
305 margin-right: 2px;
306 margin-left: 3px;
307 }
308 #battery {
309 margin-right: 3px;
310 }
311 #battery.discharging {
312 color: @white;
313 }
314 #battery.warning {
315 color: @orange;
316 }
317 #battery.critical {
318 color: @red;
319 }
320 #battery.charging {
321 color: @white;
322 }
323 #idle_inhibitor.activated {
324 color: @white;
325 }
326 #custom-worktime.running, #custom-worktime-today.running {
327 color: @white;
328 }
329 #custom-worktime.over, #custom-worktime-today.over {
330 color: @orange;
331 }
332
333 #idle_inhibitor {
334 padding-top: 1px;
335 }
336
337 #privacy {
338 color: @red;
339 margin: -1px 4px 0px 3px;
340 }
341 #clock {
342 /* margin-right: 5px; */
343 }
344 '';
345 };
346 };
347}