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.nix617
-rw-r--r--accounts/gkleen@sif/niri/mako.nix107
-rw-r--r--accounts/gkleen@sif/niri/waybar.nix338
3 files changed, 1062 insertions, 0 deletions
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
new file mode 100644
index 00000000..b895645e
--- /dev/null
+++ b/accounts/gkleen@sif/niri/default.nix
@@ -0,0 +1,617 @@
1{ config, hostConfig, pkgs, lib, ... }:
2let
3 niri = config.programs.niri.package;
4 terminal = lib.getExe config.programs.kitty.package;
5 lightctl = lib.getExe' config.services.avizo.package "lightctl";
6 volumectl = lib.getExe' config.services.avizo.package "volumectl";
7 makoctl = lib.getExe' config.services.mako.package "makoctl";
8 loginctl = lib.getExe' hostConfig.systemd.package "loginctl";
9 systemctl = lib.getExe' hostConfig.systemd.package "systemctl";
10
11 focus_or_spawn = pkgs.writeShellApplication {
12 name = "focus-or-spawn";
13 runtimeInputs = [ niri pkgs.gojq pkgs.gnugrep pkgs.socat ];
14 text = ''
15 window_select="$1"
16 shift
17 workspace_name="$1"
18 shift
19
20 workspaces_json="$(niri msg -j workspaces)"
21 workspace_output="$(jq -r --arg workspace_name "$workspace_name" '.[] | select(.name == $workspace_name) | .output' <<<"$workspaces_json")"
22 # active_workspace="$(jq -r --arg workspace_output "$workspace_output" '.[] | select(.output == $workspace_output and .is_active) | .id' <<<"$workspaces_json")"
23 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
24 if [[ $workspace_output != "$active_output" ]]; then
25 niri msg action move-workspace-to-monitor --reference "$workspace_name" "$active_output"
26 # socat STDIO "$NIRI_SOCKET" <<<'{"Action":{"FocusWorkspace":{"reference":{"Id":'"''${active_workspace}"'}}}}'
27 # niri msg action move-workspace-to-index --reference "$workspace_name" 1
28 fi
29
30 while IFS=$'\n' read -r window_json; do
31 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then
32 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
33 exit 0
34 fi
35 done < <(niri msg -j windows | jq -c '.[]')
36
37 exec "$@"
38 '';
39 };
40 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn);
41 focus-or-spawn-action-app_id = app_id: focus-or-spawn-action ''select(.app_id == "${app_id}")'';
42
43 with_adjacent_workspace = pkgs.writeShellApplication {
44 name = "with-adjacent-workspace";
45 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
46 text = ''
47 blacklist="$1"
48 shift
49 direction="$1"
50 shift
51 action="$1"
52 shift
53
54 workspaces_json="$(niri msg -j workspaces)"
55 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
56 workspace_output="$(jq -r --arg active_workspace "$active_workspace" '.[] | select(.id == ($active_workspace | tonumber)) | .output' <<<"$workspaces_json")"
57 workspace_idx="$(jq -r '.[] | select(.is_focused) | .idx' <<<"$workspaces_json")"
58
59 jq_script='map(select('
60 case "$direction" in
61 down)
62 # shellcheck disable=SC2016
63 jq_script=''${jq_script}'.idx > ($workspace_idx | tonumber)';;
64 up)
65 # shellcheck disable=SC2016
66 jq_script=''${jq_script}'.idx < ($workspace_idx | tonumber)';;
67 esac
68 # shellcheck disable=SC2016
69 jq_script=''${jq_script}' and .output == $workspace_output and ((.name == null) or (.name | test($blacklist) | not)))) | sort_by(.idx)'
70 [[ $direction == "up" ]] && jq_script=''${jq_script}' | reverse'
71 jq_script=''${jq_script}' | .[0]'
72
73 workspace_json=$(jq -c --arg blacklist "$blacklist" --arg workspace_output "$workspace_output" --arg workspace_idx "$workspace_idx" "$jq_script" <<<"$workspaces_json")
74 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
75 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
76 '';
77 };
78 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^pwctl|eff|kpxc|bmgr|edit|term$";
79 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
80 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
81
82 with_unnamed_workspace = pkgs.writeShellApplication {
83 name = "with-unnamed-workspace";
84 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
85 text = ''
86 action="$1"
87 shift
88
89 workspaces_json="$(niri msg -j workspaces)"
90 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
91 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
92
93 workspace_json="$(jq -c --arg active_output "$active_output" 'map(select(.output == $active_output and .name == null)) | sort_by(.idx) | .[0]' <<<"$workspaces_json")"
94 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
95 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
96 '';
97 };
98 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
99
100 with_select_window = pkgs.writeShellApplication {
101 name = "with-select-window";
102 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
103 text = ''
104 window_select="$1"
105 shift
106 action="$1"
107 shift
108
109 windows_json="$(niri msg -j windows)"
110 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
111 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)"
112 # shellcheck disable=SC2016
113 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")"
114
115 [[ -z "$window_json" ]] && exit 1
116
117 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
118 '';
119 };
120 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
121in {
122 imports = [
123 ./waybar.nix
124 ./mako.nix
125 ];
126
127 config = {
128 systemd.user.services.xwayland-satellite = {
129 Unit = {
130 BindsTo = [ "graphical-session.target" ];
131 PartOf = [ "graphical-session.target" ];
132 After = [ "graphical-session.target" ];
133 Requisite = [ "graphical-session.target" ];
134 };
135 Service = {
136 Type = "notify";
137 NotifyAccess = "all";
138 Environment = [ "DISPLAY=:0" ];
139 ExecStart = ''${lib.getExe pkgs.xwayland-satellite-unstable} ''${DISPLAY}'';
140 ExecStartPre = "${systemctl} --user import-environment DISPLAY";
141 StandardOutput = "journal";
142 };
143 Install = {
144 WantedBy = [ "graphical-session.target" ];
145 };
146 };
147
148 services.swayidle = {
149 events = [
150 { event = "after-resume"; command = "${lib.getExe niri} msg action power-on-monitors"; }
151 ];
152 timeouts = [
153 { timeout = 300;
154 command = "${lib.getExe niri} msg action power-off-monitors";
155 }
156 ];
157 };
158
159 programs.niri.settings = {
160 prefer-no-csd = true;
161 screenshot-path = "${config.home.homeDirectory}/screenshots";
162
163 hotkey-overlay.skip-at-startup = true;
164
165 input = {
166 keyboard.xkb = {
167 layout = "us,us";
168 variant = "dvp,";
169 options = "compose:caps,grp:win_space_toggle";
170 };
171
172 workspace-auto-back-and-forth = true;
173 # focus-follows-mouse.enable = true;
174 warp-mouse-to-focus = true;
175 };
176
177 outputs = {
178 "eDP-1" = {
179 scale = 1.5;
180 position = { x = 0; y = 0; };
181 };
182 "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" = {
183 scale = 1.5;
184 position = { x = 2560; y = 0; };
185 };
186 "HP Inc. HP 727pu CN4417143K" = {
187 mode = { width = 2560; height = 1440; refresh = 119.998; };
188 scale = 1;
189 position = { x = 2560; y = 0; };
190 variable-refresh-rate = "on-demand";
191 };
192 };
193
194 environment = {
195 NIXOS_OZONE_WL = "1";
196 QT_QPA_PLATFORM = "wayland";
197 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
198 GDK_BACKEND = "wayland";
199 SDL_VIDEODRIVER = "wayland";
200 DISPLAY = ":0";
201 };
202
203 debug.render-drm-device = "/dev/dri/by-path/pci-0000:00:02.0-render";
204
205 layout = {
206 gaps = 8;
207 struts = { left = 0; right = 0; top = 0; bottom = 0; };
208 focus-ring = {
209 width = 2;
210 active.gradient = {
211 from = "hsla(195 100% 60% 0.75)";
212 to = "hsla(155 100% 50% 0.75)";
213 angle = 29;
214 relative-to = "workspace-view";
215 };
216 inactive.gradient = {
217 from = "hsla(0 0% 42% 0.66)";
218 to = "hsla(0 0% 35% 0.66)";
219 angle = 29;
220 relative-to = "workspace-view";
221 };
222 };
223
224 preset-column-widths = [
225 { proportion = 1. / 4.; }
226 { proportion = 1. / 3.; }
227 { proportion = 1. / 2.; }
228 { proportion = 2. / 3.; }
229 { proportion = 3. / 4.; }
230 ];
231 default-column-width.proportion = 1. / 2.;
232 preset-window-heights = [
233 { proportion = 1. / 3.; }
234 { proportion = 1. / 2.; }
235 { proportion = 2. / 3.; }
236 { proportion = 1.; }
237 ];
238
239 always-center-single-column = true;
240 };
241
242 cursor.hide-when-typing = true;
243
244 input = {
245 touchpad.enable = false;
246 trackball = {
247 scroll-method = "on-button-down";
248 scroll-button = 278;
249 };
250 };
251
252 workspaces = {
253 "001" = { name = "pwctl"; open-on-output = "eDP-1"; };
254 "002" = { name = "kpxc"; open-on-output = "eDP-1"; };
255 "003" = { name = "bmgr"; open-on-output = "eDP-1"; };
256 "004" = { name = "term"; open-on-output = "eDP-1"; };
257 "005" = { name = "edit"; open-on-output = "eDP-1"; };
258 "006" = { name = "eff"; open-on-output = "eDP-1"; };
259 "101".name = "comm";
260 "102".name = "web";
261 # "104".name = "read";
262 # "105".name = "mon";
263 "110".name = "vid";
264 "120".name = "bmr";
265 };
266
267 window-rules = [
268 # {
269 # geometry-corner-radius =
270 # let
271 # allCorners = r: { bottom-left = r; bottom-right = r; top-left = r; top-right = r; };
272 # in allCorners 4.;
273 # clip-to-geometry = true;
274 # }
275 {
276 matches = [ { app-id = "^com\.saivert\.pwvucontrol$"; } ];
277 open-on-workspace = "pwctl";
278 open-maximized = true;
279 }
280 {
281 matches = [ { app-id = "^com\.github\.wwmm\.easyeffects$"; } ];
282 open-on-workspace = "eff";
283 open-maximized = true;
284 }
285 {
286 matches = [ { app-id = "^\.blueman-manager-wrapped$"; } ];
287 open-on-workspace = "bmgr";
288 open-maximized = true;
289 }
290 {
291 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
292 block-out-from = "screencast";
293 }
294 {
295 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
296 excludes = [
297 { title = "^Unlock Database.*"; }
298 { title = "^Access Request.*"; }
299 { title = ".*Passkey credentials$"; }
300 ];
301 open-on-workspace = "kpxc";
302 open-maximized = true;
303 open-focused = false;
304 }
305 {
306 matches = [
307 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Unlock Database.*"; }
308 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Access Request.*"; }
309 { app-id = "^org\.keepassxc\.KeePassXC$"; title = ".*Passkey credentials$"; }
310 ];
311 open-focused = true;
312 }
313 {
314 matches = [ { app-id = "^kitty-scratch$"; } ];
315 open-on-workspace = "term";
316 open-maximized = true;
317 }
318 {
319 matches = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
320 open-on-workspace = "edit";
321 open-maximized = true;
322 }
323 {
324 matches = [
325 { app-id = "^emacs$"; }
326 { app-id = "^firefox$"; }
327 ];
328 default-column-width.proportion = 2. / 3.;
329 }
330 {
331 matches = [
332 { app-id = "^kitty$"; }
333 { app-id = "^kitty-play$"; }
334 ];
335 default-column-width.proportion = 1. / 3.;
336 }
337 {
338 matches = [
339 { app-id = "^thunderbird$"; }
340 { app-id = "^Element$"; }
341 ];
342 open-on-workspace = "comm";
343 }
344 {
345 matches = [ { app-id = "^firefox$"; } ];
346 open-on-workspace = "web";
347 open-maximized = true;
348 variable-refresh-rate = true;
349 }
350 # {
351 # matches = [
352 # { app-id = "^evince$"; }
353 # { app-id = "^imv$"; }
354 # { app-id = "^org\.pwmt\.zathura$"; }
355 # ];
356 # open-on-workspace = "read";
357 # }
358 {
359 matches = [ { app-id = "^mpv$"; } ];
360 open-on-workspace = "vid";
361 default-column-width.proportion = 1.;
362 variable-refresh-rate = true;
363 }
364 {
365 matches = [ { app-id = "^kitty-play$"; } ];
366 open-on-workspace = "vid";
367 open-focused = false;
368 }
369 # {
370 # matches = [
371 # { app-id = "^qemu$"; }
372 # { app-id = "^virt-manager$"; }
373 # ];
374 # open-on-workspace = "mon";
375 # }
376 {
377 matches = [ { app-id = "^pdfpc$"; } ];
378 default-column-width.proportion = 1.;
379 }
380 {
381 matches = [ { app-id = "^pdfpc$"; title = "^pdfpc - presentation"; } ];
382 open-on-workspace = "bmr";
383 open-fullscreen = true;
384 }
385 {
386 matches = [
387 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
388 { app-id = "^org\.kde\.polkit-kde-authentication-agent-1$"; }
389 ];
390 open-floating = true;
391 }
392 ];
393 layer-rules = [
394 { matches = [
395 { namespace = "^notifications$"; }
396 { namespace = "^waybar$"; }
397 ];
398 block-out-from = "screencast";
399 }
400 ];
401
402 binds = with config.lib.niri.actions; {
403 "Mod+Slash".action = show-hotkey-overlay;
404
405 "Mod+Return".action = spawn terminal;
406 "Mod+Q".action = close-window;
407 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
408 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
409
410 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
411 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
412 name = "queue-yt-dlp";
413 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
414 text = ''
415 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
416 '';
417 }));
418 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
419 name = "queue-yt-dlp";
420 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
421 text = ''
422 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
423 '';
424 }));
425
426 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
427 name = "qalc-fuzzel";
428 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
429 text = ''
430 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
431 prev() {
432 FOUND=false
433 while IFS= read -r line; do
434 [[ -n "$line" ]] || continue
435 FOUND=true
436 echo "$line"
437 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)
438 $FOUND || echo
439 }
440 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
441 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
442 QALC_RES="$FUZZEL_RES"
443 QALC_RET=0
444 else
445 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
446 QALC_RET=$?
447 fi
448 [[ -n "$QALC_RES" ]] || exit 1
449 EXISTING=false
450 set +o pipefail
451 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
452 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
453 set -o pipefail
454 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
455 set +o pipefail
456 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
457 set -o pipefail
458 cat >"$RES_FILE" <<<"$QALC_RES"
459 fi
460 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
461 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
462 notify-send "$QALC_RES"
463 '';
464 }));
465 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
466 name = "emoji-fuzzel";
467 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
468 text = ''
469 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
470 [[ -n "$FUZZEL_RES" ]] || exit 1
471 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
472 '';
473 }));
474 "Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
475 name = "screenshot";
476 runtimeInputs = with pkgs; [ grim slurp wl-clipboard-rs coreutils ];
477 text = ''
478 grim -g "$(slurp -b 00000080 -c FFFFFFFF -s 00000000 -w 1)" - \
479 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
480 | wl-copy --type image/png
481 '';
482 }));
483 "Shift+Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
484 name = "screenshot";
485 runtimeInputs = with pkgs; [ grim niri gojq wl-clipboard-rs coreutils ];
486 text = ''
487 grim -o "$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .output')" - \
488 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
489 | wl-copy --type image/png
490 '';
491 }));
492 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
493 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
494
495 "Mod+H".action = focus-column-left;
496 "Mod+T".action = focus-window-down;
497 "Mod+N".action = focus-window-up;
498 "Mod+S".action = focus-column-right;
499
500 "Mod+Shift+H".action = move-column-left;
501 "Mod+Shift+T".action = move-window-down;
502 "Mod+Shift+N".action = move-window-up;
503 "Mod+Shift+S".action = move-column-right;
504
505 "Mod+Control+H".action = focus-monitor-left;
506 "Mod+Control+T".action = focus-monitor-down;
507 "Mod+Control+N".action = focus-monitor-up;
508 "Mod+Control+S".action = focus-monitor-right;
509
510 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
511 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
512 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
513 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
514
515 "Mod+G".action = focus-adjacent-workspace "down";
516 "Mod+C".action = focus-adjacent-workspace "up";
517
518 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
519 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
520
521 "Mod+Shift+Control+G".action = move-workspace-down;
522 "Mod+Shift+Control+C".action = move-workspace-up;
523
524 "Mod+ParenLeft".action = focus-workspace "comm";
525 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
526
527 "Mod+ParenRight".action = focus-workspace "web";
528 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
529
530 "Mod+BraceRight".action = focus-workspace "read";
531 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
532
533 "Mod+BraceLeft".action = focus-workspace "mon";
534 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
535
536 "Mod+Asterisk".action = focus-workspace "vid";
537 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
538
539 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
540 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
541
542 "Mod+M".action = consume-or-expel-window-left;
543 "Mod+W".action = consume-or-expel-window-right;
544
545 "Mod+R".action = switch-preset-column-width;
546 "Mod+Shift+R".action = switch-preset-window-height;
547 "Mod+F".action = center-column;
548 "Mod+Shift+F".action = maximize-column;
549 "Mod+Shift+Ctrl+F".action = fullscreen-window;
550
551 "Mod+V".action = switch-focus-between-floating-and-tiling;
552 "Mod+Shift+V".action = toggle-window-floating;
553
554 "Mod+Left".action = set-column-width "-10%";
555 "Mod+Down".action = set-window-height "-10%";
556 "Mod+Up".action = set-window-height "+10%";
557 "Mod+Right".action = set-column-width "+10%";
558
559 "Mod+Shift+Z" = {
560 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
561 allow-when-locked = true;
562 };
563 "Mod+Shift+L".action = spawn loginctl "lock-session";
564 "Mod+Shift+E".action = quit;
565 "Mod+Shift+Minus" = {
566 action = spawn systemctl "suspend";
567 allow-when-locked = true;
568 };
569 "Mod+Shift+Control+Minus" = {
570 action = spawn systemctl "hibernate";
571 allow-when-locked = true;
572 };
573 "Mod+Shift+P" = {
574 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
575 allow-when-locked = true;
576 };
577
578 "XF86MonBrightnessUp" = {
579 action = spawn lightctl "-d" "-e4" "-n1" "up";
580 allow-when-locked = true;
581 };
582 "XF86MonBrightnessDown" = {
583 action = spawn lightctl "-d" "-e4" "-n1" "down";
584 allow-when-locked = true;
585 };
586 "XF86AudioRaiseVolume" = {
587 action = spawn volumectl "-d" "-u" "up";
588 allow-when-locked = true;
589 };
590 "XF86AudioLowerVolume" = {
591 action = spawn volumectl "-d" "-u" "down";
592 allow-when-locked = true;
593 };
594 "XF86AudioMute" = {
595 action = spawn volumectl "-d" "toggle-mute";
596 allow-when-locked = true;
597 };
598 "XF86AudioMicMute" = {
599 action = spawn volumectl "-d" "-m" "toggle-mute";
600 allow-when-locked = true;
601 };
602
603 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
604 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
605 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
606 "Mod+Comma".action = spawn makoctl "restore";
607
608 "Mod+Control+A".action = focus-or-spawn-action-app_id "com.saivert.pwvucontrol" "pwctl" "pwvucontrol";
609 "Mod+Control+O".action = focus-or-spawn-action-app_id "com.github.wwmm.easyeffects" "eff" "easyeffects";
610 "Mod+Control+P".action = focus-or-spawn-action-app_id "org.keepassxc.KeePassXC" "kpxc" "keepassxc";
611 "Mod+Control+B".action = focus-or-spawn-action-app_id ".blueman-manager-wrapped" "bmgr" "blueman-manager";
612 "Mod+Control+Return".action = focus-or-spawn-action-app_id "kitty-scratch" "term" "kitty" "--app-id" "kitty-scratch";
613 "Mod+Control+E".action = focus-or-spawn-action "select(.app_id == \"emacs\" and .title == \"scratch\")" "edit" "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))";
614 };
615 };
616 };
617}
diff --git a/accounts/gkleen@sif/niri/mako.nix b/accounts/gkleen@sif/niri/mako.nix
new file mode 100644
index 00000000..8fbc81c1
--- /dev/null
+++ b/accounts/gkleen@sif/niri/mako.nix
@@ -0,0 +1,107 @@
1{ config, lib, pkgs, ... }:
2{
3 config = {
4 services.mako = {
5 enable = true;
6 font = "Fira Sans 10";
7 format = "<i>%s</i>\\n%b";
8 margin = "2";
9 maxVisible = -1;
10 backgroundColor = "#000000dd";
11 progressColor = "source #223544ff";
12 width = 384;
13 extraConfig = ''
14 outer-margin=1
15 max-history=100
16 max-icon-size=48
17
18 [grouped]
19 format=<b>(%g)</b> <i>%s</i>\n%b
20
21 [urgency=low]
22 text-color=#999999ff
23
24 [urgency=critical]
25 background-color=#900000dd
26
27 [app-name=Element]
28 group-by=summary
29
30 [mode=silent]
31 invisible=1
32 '';
33 };
34 systemd.user.services.mako = {
35 Unit = {
36 Description = "Mako notification daemon";
37 PartOf = [ "graphical-session.target" ];
38 };
39 Install = {
40 WantedBy = [ "graphical-session.target" ];
41 };
42 Service = {
43 Type = "dbus";
44 BusName = "org.freedesktop.Notifications";
45 ExecStart = lib.getExe config.services.mako.package;
46 RestartSec = 5;
47 Restart = "always";
48 };
49 };
50
51 systemd.user.services.mako-follows-focus = {
52 Unit = {
53 BindsTo = [ "niri.service" "mako.service" ];
54 After = [ "niri.service" "mako.service" ];
55 };
56 Service = {
57 Type = "simple";
58 Restart = "always";
59 ExecStart = pkgs.writers.writePython3 "mako-follows-focus" {
60 libraries = with pkgs.python3Packages; [];
61 } ''
62 import os
63 import socket
64 import json
65 import subprocess
66
67
68 current_output = None
69 workspaces = []
70
71
72 def output_changed(new_output):
73 global current_output
74
75 if current_output == new_output:
76 return
77
78 current_output = new_output
79 subprocess.run(["makoctl", "reload"])
80
81
82 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
83 sock.connect(os.environ["NIRI_SOCKET"])
84 sock.send(b"\"EventStream\"\n")
85 for line in sock.makefile(buffering=1, encoding='utf-8'):
86 if line_json := json.loads(line):
87 if "WorkspacesChanged" in line_json:
88 workspaces = line_json["WorkspacesChanged"]["workspaces"]
89 for workspace in workspaces:
90 if not workspace["is_focused"]:
91 continue
92 output_changed(workspace["output"])
93 break
94 if "WorkspaceActivated" in line_json and line_json["WorkspaceActivated"]["focused"]: # noqa: E501
95 for workspace in workspaces:
96 if not workspace["id"] == line_json["WorkspaceActivated"]["id"]: # noqa: E501
97 continue
98 output_changed(workspace["output"])
99 break
100 '';
101 };
102 Install = {
103 WantedBy = [ "mako.service" ];
104 };
105 };
106 };
107}
diff --git a/accounts/gkleen@sif/niri/waybar.nix b/accounts/gkleen@sif/niri/waybar.nix
new file mode 100644
index 00000000..79c429f8
--- /dev/null
+++ b/accounts/gkleen@sif/niri/waybar.nix
@@ -0,0 +1,338 @@
1{ lib, pkgs, ... }:
2{
3 config = {
4 programs.waybar = {
5 enable = true;
6 systemd = {
7 enable = true;
8 target = "graphical-session.target";
9 };
10 settings = let
11 windowRewrites = {
12 "(.*) — Mozilla Firefox" = "$1";
13 "(.*) - Mozilla Thunderbird" = "$1";
14 "(.*) - mpv" = "$1";
15 };
16 iconSize = 11;
17 in [
18 {
19 layer = "top";
20 position = "top";
21 height = 14;
22 output = [ "eDP-1" "DP-2" "DP-3" ];
23 modules-left = [ "niri/workspaces" ];
24 modules-center = [ "niri/window" ];
25 modules-right = [ # "custom/worktime" "custom/worktime-today"
26 "custom/weather"
27 "custom/keymap"
28 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "clock" ];
29
30 "custom/mako" = {
31 format = "{}";
32 return-type = "json";
33 exec = pkgs.writers.writePython3 "mako-silent" { libraries = [ pkgs.python3Packages.dbus-next ]; } ''
34 from dbus_next.aio import MessageBus
35
36 import asyncio
37
38 import json
39
40
41 loop = asyncio.new_event_loop()
42 asyncio.set_event_loop(loop)
43
44
45 async def main():
46 bus = await MessageBus().connect()
47 # the introspection xml would normally be included in your project, but
48 # this is convenient for development
49 introspection = await bus.introspect('org.freedesktop.Notifications', '/fr/emersion/Mako') # noqa: E501
50
51 obj = bus.get_proxy_object('org.freedesktop.Notifications', '/fr/emersion/Mako', introspection) # noqa: E501
52 mako = obj.get_interface('fr.emersion.Mako')
53 properties = obj.get_interface('org.freedesktop.DBus.Properties')
54
55 async def print_mode():
56 modes = await mako.get_modes()
57 is_silent = "silent" in modes
58 icon = "&#xf009b;" if is_silent else "&#xf009a;"
59 text = f"<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>" # noqa: E501
60 if is_silent:
61 text = f"<span color=\"#ffffff\">{text}</span>"
62 print(json.dumps({'text': text}, separators=(',', ':')), flush=True) # noqa: E501
63
64 async def on_properties_changed(interface_name, changed_properties, invalidated_properties): # noqa: E501
65 if "Modes" not in invalidated_properties:
66 return
67
68 await print_mode()
69
70 properties.on_properties_changed(on_properties_changed)
71 await print_mode()
72
73 await loop.create_future()
74
75
76 loop.run_until_complete(main())
77 '';
78 on-click = "makoctl mode -t silent";
79 };
80 "custom/weather" = {
81 format = "{}";
82 tooltip = true;
83 interval = 3600;
84 exec = "${lib.getExe pkgs.wttrbar} --hide-conditions --nerd --custom-indicator \"<span font=\\\"Symbols Nerd Font Mono\\\" size=\\\"100%\\\">{ICON}</span> {FeelsLikeC}°\"";
85 return-type = "json";
86 };
87 "custom/keymap" = {
88 format = "{}";
89 tooltip = true;
90 return-type = "json";
91 exec = pkgs.writers.writePython3 "keymap" {} ''
92 import os
93 import socket
94 import json
95
96
97 def output(keymap):
98 short = keymap
99 if keymap == "English (programmer Dvorak)":
100 short = "dvp"
101 elif keymap == "English (US)":
102 short = "<span color=\"#ffffff\">us</span>"
103 print(json.dumps({'text': short, 'tooltip': keymap}, separators=(',', ':')), flush=True) # noqa: E501
104
105
106 keyboard_layouts = []
107
108 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
109 sock.connect(os.environ["NIRI_SOCKET"])
110 sock.send(b"\"EventStream\"\n")
111 for line in sock.makefile(buffering=1, encoding='utf-8'):
112 if line_json := json.loads(line):
113 if "KeyboardLayoutsChanged" in line_json:
114 keyboard_layouts = line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["names"] # noqa: E501
115 output(keyboard_layouts[line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["current_idx"]]) # noqa: E501
116 if "KeyboardLayoutSwitched" in line_json:
117 output(keyboard_layouts[line_json["KeyboardLayoutSwitched"]["idx"]]) # noqa: E501
118 '';
119 on-click = "niri msg action switch-layout next";
120 };
121 "custom/worktime" = {
122 interval = 60;
123 exec = lib.getExe pkgs.worktime;
124 tooltip = false;
125 };
126 "custom/worktime-today" = {
127 interval = 60;
128 exec = "${lib.getExe pkgs.worktime} today";
129 tooltip = false;
130 };
131 "niri/workspaces" = {
132 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"];
133 };
134 "niri/window" = {
135 separate-outputs = true;
136 icon = true;
137 icon-size = 14;
138 rewrite = windowRewrites;
139 };
140 clock = {
141 interval = 1;
142 # timezone = "Europe/Berlin";
143 format = "W{:%V-%u %F %H:%M:%S%Ez}";
144 tooltip-format = "<tt><small>{calendar}</small></tt>";
145 calendar = {
146 mode = "year";
147 mode-mon-col = 3;
148 weeks-pos = "left";
149 on-scroll = 1;
150 format = {
151 months = "<span color='#ffead3'><b>{}</b></span>";
152 days = "{}";
153 weeks = "<span color='#99ffdd'><b>{}</b></span>";
154 weekdays = "<span color='#ffcc66'><b>{}</b></span>";
155 today = "<span color='#ff6699'><b>{}</b></span>";
156 };
157 };
158 };
159 battery = {
160 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
161 icon-size = iconSize - 2;
162 states = { warning = 30; critical = 15; };
163 format-icons = ["&#xf008e;" "&#xf007a;" "&#xf007b;" "&#xf007c;" "&#xf007d;" "&#xf007e;" "&#xf007f;" "&#xf0080;" "&#xf0081;" "&#xf0082;" "&#xf0079;" ];
164 format-charging = "&#xf0084;";
165 format-plugged = "&#xf06a5;";
166 tooltip-format = "{capacity}% {timeTo}";
167 interval = 20;
168 };
169 tray = {
170 icon-size = 16;
171 # show-passive-items = true;
172 spacing = 1;
173 };
174 privacy = {
175 icon-spacing = 7;
176 icon-size = iconSize;
177 modules = [
178 { type = "screenshare"; }
179 { type = "audio-in"; }
180 ];
181 };
182 idle_inhibitor = {
183 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
184 icon-size = iconSize;
185 format-icons = { activated = "&#xf0208;"; deactivated = "&#xf0209;"; };
186 timeout = 120;
187 };
188 backlight = {
189 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
190 icon-size = iconSize;
191 tooltip-format = "{percent}%";
192 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"];
193 on-scroll-up = "lightctl -d -e4 -n1 up";
194 on-scroll-down = "lightctl -d -e4 -n1 down";
195 };
196 wireplumber = {
197 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
198 icon-size = iconSize;
199 tooltip-format = "{volume}% {node_name}";
200 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"];
201 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>";
202 # ignored-sinks = ["Easy Effects Sink"];
203 on-scroll-up = "volumectl -d -u up";
204 on-scroll-down = "volumectl -d -u down";
205 on-click = "volumectl -d toggle-mute";
206 };
207 }
208 {
209 layer = "top";
210 position = "top";
211 height = 14;
212 output = [ "!eDP-1" "!DP-2" "!DP-3" ];
213 modules-left = [ "niri/workspaces" ];
214 modules-center = [ "niri/window" ];
215 modules-right = [ "clock" ];
216
217 "niri/workspaces" = {
218 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"];
219 };
220 "niri/window" = {
221 separate-outputs = true;
222 icon = true;
223 icon-size = 14;
224 rewrite = windowRewrites;
225 };
226 clock = {
227 interval = 1;
228 # timezone = "Europe/Berlin";
229 format = "{:%H:%M}";
230 tooltip-format = "W{:%V-%u %F %H:%M:%S%Ez}";
231 };
232 }
233 ];
234 style = ''
235 @define-color white #ffffff;
236 @define-color grey #555555;
237 @define-color blue #1a8fff;
238 @define-color green #23fd00;
239 @define-color orange #f28a21;
240 @define-color red #f2201f;
241
242 * {
243 border: none;
244 font-family: "Fira Sans Nerd Font";
245 font-size: 10pt;
246 min-height: 0;
247 }
248
249 window#waybar {
250 background-color: rgba(0, 0, 0, 0.66);
251 color: @white;
252 }
253
254 .modules-left {
255 margin-left: 8px;
256 }
257 .modules-right {
258 margin-right: 8px;
259 }
260
261 .module {
262 margin: 0 5px;
263 }
264
265 #workspaces button {
266 color: @white;
267 padding: 2px 5px;
268 }
269 #workspaces button.empty {
270 color: @grey;
271 }
272 #workspaces button.active {
273 color: @green;
274 }
275 #workspaces button.urgent {
276 color: @red;
277 }
278
279 #custom-weather, #custom-keymap, #custom-worktime, #custom-worktime-today {
280 color: @grey;
281 margin: 0 5px;
282 }
283 #custom-weather, #custom-worktime-today {
284 margin-right: 3px;
285 }
286 #custom-keymap, #custom-weather {
287 margin-left: 3px;
288 }
289
290 #tray {
291 margin: 0;
292 }
293 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako {
294 color: @grey;
295 margin: 0 5px 0 2px;
296 }
297 #idle_inhibitor {
298 margin-right: 4px;
299 margin-left: 6px;
300 }
301 #custom-mako {
302 margin-right: 2px;
303 margin-left: 3px;
304 }
305 #battery {
306 margin-right: 3px;
307 }
308 #battery.discharging {
309 color: @white;
310 }
311 #battery.warning {
312 color: @orange;
313 }
314 #battery.critical {
315 color: @red;
316 }
317 #battery.charging {
318 color: @white;
319 }
320 #idle_inhibitor.activated {
321 color: @white;
322 }
323
324 #idle_inhibitor {
325 padding-top: 1px;
326 }
327
328 #privacy {
329 color: @red;
330 margin: -1px 2px 0px 5px;
331 }
332 #clock {
333 /* margin-right: 5px; */
334 }
335 '';
336 };
337 };
338}