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.nix1227
-rw-r--r--accounts/gkleen@sif/niri/mako.nix52
-rw-r--r--accounts/gkleen@sif/niri/swayosd.nix7
-rw-r--r--accounts/gkleen@sif/niri/waybar.nix31
4 files changed, 799 insertions, 518 deletions
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
index 7e187c84..8752f3e3 100644
--- a/accounts/gkleen@sif/niri/default.nix
+++ b/accounts/gkleen@sif/niri/default.nix
@@ -1,6 +1,10 @@
1{ config, hostConfig, pkgs, lib, ... }: 1{ config, hostConfig, pkgs, lib, flakeInputs, ... }:
2let 2let
3 niri = config.programs.niri.package; 3 cfg = config.programs.niri;
4
5 kdl = flakeInputs.niri-flake.lib.kdl;
6
7 niri = cfg.package;
4 terminal = lib.getExe config.programs.kitty.package; 8 terminal = lib.getExe config.programs.kitty.package;
5 makoctl = lib.getExe' config.services.mako.package "makoctl"; 9 makoctl = lib.getExe' config.services.mako.package "makoctl";
6 loginctl = lib.getExe' hostConfig.systemd.package "loginctl"; 10 loginctl = lib.getExe' hostConfig.systemd.package "loginctl";
@@ -31,7 +35,11 @@ let
31 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then 35 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then
32 niri msg action focus-workspace-previous 36 niri msg action focus-workspace-previous
33 else 37 else
34 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")" 38 if [[ $(jq -r --arg workspace_name "$workspace_name" 'map(select(.name == $workspace_name)) | .[0].is_focused' <<<"$workspaces_json") != "true" ]] && [[ $(jq -r --arg workspace_name "$workspace_name" 'map(select(.name == $workspace_name)) | .[0].id' <<<"$workspaces_json") = $(jq -r '.workspace_id' <<<"$window_json") ]]; then
39 niri msg action focus-workspace "$workspace_name"
40 else
41 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
42 fi
35 fi 43 fi
36 exit 0 44 exit 0
37 fi 45 fi
@@ -41,7 +49,6 @@ let
41 ''; 49 '';
42 }; 50 };
43 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn); 51 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn);
44 focus-or-spawn-action-app_id = app_id: focus-or-spawn-action ''select(.app_id == "${app_id}")'';
45 52
46 with_adjacent_workspace = pkgs.writeShellApplication { 53 with_adjacent_workspace = pkgs.writeShellApplication {
47 name = "with-adjacent-workspace"; 54 name = "with-adjacent-workspace";
@@ -78,9 +85,9 @@ let
78 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET" 85 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
79 ''; 86 '';
80 }; 87 };
81 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^pwctl|eff|kpxc|bmgr|edit|term$"; 88 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^${lib.concatMapStringsSep "|" ({ name, ...}: name) cfg.scratchspaces}$";
82 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}''; 89 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
83 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}''; 90 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
84 91
85 with_unnamed_workspace = pkgs.writeShellApplication { 92 with_unnamed_workspace = pkgs.writeShellApplication {
86 name = "with-unnamed-workspace"; 93 name = "with-unnamed-workspace";
@@ -101,6 +108,21 @@ let
101 }; 108 };
102 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace); 109 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
103 110
111 with_empty_unnamed_workspace = pkgs.writeShellApplication {
112 name = "with-empty-unnamed-workspace";
113 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
114 text = ''
115 action="$1"
116 shift
117
118 workspaces_json="$(niri msg -j workspaces)"
119 active_output="$(jq '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
120 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")"
121 jq --argjson workspace_id "$target_workspace_id" -nc "$action" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
122 '';
123 };
124 with-empty-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_empty_unnamed_workspace);
125
104 with_select_window = pkgs.writeShellApplication { 126 with_select_window = pkgs.writeShellApplication {
105 name = "with-select-window"; 127 name = "with-select-window";
106 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ]; 128 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
@@ -112,7 +134,7 @@ let
112 134
113 windows_json="$(niri msg -j windows)" 135 windows_json="$(niri msg -j windows)"
114 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")" 136 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
115 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)" 137 window_ix="$(gojq -r --arg active_workspace "$active_workspace" '.[] | select('"$window_select"') | "\(.title)\u0000icon\u001f\(.app_id)"' <<<"$windows_json" | fuzzel --width=60 --log-level=warning --dmenu --index)"
116 # shellcheck disable=SC2016 138 # shellcheck disable=SC2016
117 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")" 139 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")"
118 140
@@ -122,6 +144,25 @@ let
122 ''; 144 '';
123 }; 145 };
124 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window); 146 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
147
148 with_predicate_window = pred: pkgs.writeShellApplication {
149 name = "with-predicate-window";
150 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
151 text = ''
152 action="$1"
153 shift
154
155 windows_json="$(niri msg -j windows)"
156 window_json="$(gojq -rc 'map(select(${pred})) | .[0]' <<<"$windows_json")"
157
158 [[ -z "$window_json" || $window_json = "null" ]] && exit 1
159
160 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
161 '';
162 };
163
164 with-urgent-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_urgent"));
165 with-focused-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_focused"));
125in { 166in {
126 imports = [ 167 imports = [
127 ./waybar.nix 168 ./waybar.nix
@@ -129,6 +170,65 @@ in {
129 ./swayosd.nix 170 ./swayosd.nix
130 ]; 171 ];
131 172
173 options = {
174 programs.niri.scratchspaces = lib.mkOption {
175 type = lib.types.listOf (lib.types.submodule ({ config, ... }: {
176 options = {
177 name = lib.mkOption {
178 type = lib.types.str;
179 };
180 match = lib.mkOption {
181 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
182 default = [];
183 };
184 exclude = lib.mkOption {
185 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
186 default = [];
187 };
188 windowRuleExtra = lib.mkOption {
189 type = kdl.types.kdl-nodes;
190 default = [];
191 };
192 key = lib.mkOption {
193 type = lib.types.nullOr lib.types.str;
194 default = null;
195 };
196 moveKey = lib.mkOption {
197 type = lib.types.nullOr lib.types.str;
198 default = let
199 keys = lib.splitString "+" config.key;
200 defMoveKey = lib.concatStringsSep "+" (lib.flatten [
201 (lib.take (lib.length keys - 1) keys)
202 ["Shift"]
203 (lib.takeEnd 1 keys)
204 ]);
205 in if config.key == null then null else defMoveKey;
206 };
207 spawn = lib.mkOption {
208 type = lib.types.nullOr (lib.types.listOf lib.types.str);
209 default = null;
210 };
211 app-id = lib.mkOption {
212 type = lib.types.nullOr lib.types.str;
213 default = null;
214 };
215 selector = lib.mkOption {
216 type = lib.types.nullOr lib.types.str;
217 default = null;
218 };
219 };
220
221 config = lib.mkMerge [
222 (lib.mkIf (config.app-id != null) {
223 match = lib.mkDefault [ { app-id = "^${lib.escapeRegex config.app-id}$"; } ];
224 selector = lib.mkDefault "select(.app_id == \"${config.app-id}\")";
225 })
226 ];
227 }));
228 default = [];
229 };
230 };
231
132 config = { 232 config = {
133 systemd.user.services.xwayland-satellite = { 233 systemd.user.services.xwayland-satellite = {
134 Unit = { 234 Unit = {
@@ -155,7 +255,7 @@ in {
155 { event = "after-resume"; command = "${lib.getExe niri} msg action power-on-monitors"; } 255 { event = "after-resume"; command = "${lib.getExe niri} msg action power-on-monitors"; }
156 ]; 256 ];
157 timeouts = [ 257 timeouts = [
158 { timeout = 300; 258 { timeout = 540;
159 command = "${lib.getExe niri} msg action power-off-monitors"; 259 command = "${lib.getExe niri} msg action power-off-monitors";
160 } 260 }
161 ]; 261 ];
@@ -178,11 +278,11 @@ in {
178 Service = { 278 Service = {
179 Type = "simple"; 279 Type = "simple";
180 Sockets = [ "niri-workspace-history.socket" ]; 280 Sockets = [ "niri-workspace-history.socket" ];
181 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" {} '' 281 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" { flakeIgnore = ["E501"]; } ''
182 import os 282 import os
183 import socket 283 import socket
184 import json 284 import json
185 import sys 285 # import sys
186 from collections import defaultdict 286 from collections import defaultdict
187 from threading import Thread, Lock 287 from threading import Thread, Lock
188 from socketserver import StreamRequestHandler, ThreadingTCPServer 288 from socketserver import StreamRequestHandler, ThreadingTCPServer
@@ -206,11 +306,9 @@ in {
206 workspaces = list() 306 workspaces = list()
207 307
208 def focus_workspace(output, workspace): 308 def focus_workspace(output, workspace):
209 global workspace_history, history_lock
210
211 with history_lock: 309 with history_lock:
212 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace] # noqa: E501 310 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace]
213 print(json.dumps(workspace_history), file=sys.stderr) 311 # print(json.dumps(workspace_history), file=sys.stderr)
214 312
215 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 313 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
216 sock.connect(os.environ["NIRI_SOCKET"]) 314 sock.connect(os.environ["NIRI_SOCKET"])
@@ -232,16 +330,14 @@ in {
232 330
233 class RequestHandler(StreamRequestHandler): 331 class RequestHandler(StreamRequestHandler):
234 def handle(self): 332 def handle(self):
235 global workspace_history, history_lock 333 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out:
236
237 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out: # noqa: E501
238 with history_lock: 334 with history_lock:
239 json.dump(workspace_history, out) 335 json.dump(workspace_history, out)
240 336
241 337
242 class Server(ThreadingTCPServer): 338 class Server(ThreadingTCPServer):
243 def __init__(self): 339 def __init__(self):
244 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False) # noqa: E501 340 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False)
245 self.socket = socket.fromfd(3, self.address_family, self.socket_type) 341 self.socket = socket.fromfd(3, self.address_family, self.socket_type)
246 342
247 343
@@ -267,477 +363,652 @@ in {
267 ''; 363 '';
268 }; 364 };
269 }; 365 };
270 366 systemd.user.services.niri-workspace-sort = {
271 programs.niri.settings = { 367 Unit = {
272 prefer-no-csd = true; 368 BindsTo = [ "niri.service" ];
273 screenshot-path = "${config.home.homeDirectory}/screenshots"; 369 After = [ "niri.service" ];
274
275 hotkey-overlay.skip-at-startup = true;
276
277 input = {
278 keyboard = {
279 repeat-delay = 300;
280 repeat-rate = 50;
281
282 xkb = {
283 layout = "us,us";
284 variant = "dvp,";
285 options = "compose:caps,grp:win_space_toggle";
286 };
287 };
288
289 workspace-auto-back-and-forth = true;
290 # focus-follows-mouse.enable = true;
291 warp-mouse-to-focus = true;
292 };
293
294 outputs = {
295 "eDP-1" = {
296 scale = 1.5;
297 position = { x = 0; y = 0; };
298 };
299 "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" = {
300 scale = 1.5;
301 position = { x = 2560; y = 0; };
302 };
303 "HP Inc. HP 727pu CN4417143K" = {
304 mode = { width = 2560; height = 1440; refresh = 119.998; };
305 scale = 1;
306 position = { x = 2560; y = 0; };
307 variable-refresh-rate = "on-demand";
308 };
309 };
310
311 environment = {
312 NIXOS_OZONE_WL = "1";
313 QT_QPA_PLATFORM = "wayland";
314 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
315 GDK_BACKEND = "wayland";
316 SDL_VIDEODRIVER = "wayland";
317 DISPLAY = ":0";
318 }; 370 };
319 371 Install = {
320 debug.render-drm-device = "/dev/dri/by-path/pci-0000:00:02.0-render"; 372 WantedBy = [ "niri.service" ];
321
322 animations = {
323 slowdown = 0.5;
324 workspace-switch = null;
325 }; 373 };
374 Service = {
375 Type = "simple";
376 ExecStart = pkgs.writers.writePython3 "niri-workspace-sort" { flakeIgnore = ["E501"]; } ''
377 import os
378 import sys
379 import socket
380 import json
326 381
327 layout = { 382 outputs = None
328 gaps = 8; 383 only = {'HDMI-A-1': {'bmr'}, 'eDP-1': {'vid'}}
329 struts = { left = 0; right = 0; top = 0; bottom = 0; };
330 focus-ring = {
331 width = 2;
332 active.gradient = {
333 from = "hsla(195 100% 60% 0.75)";
334 to = "hsla(155 100% 50% 0.75)";
335 angle = 29;
336 relative-to = "workspace-view";
337 };
338 inactive.gradient = {
339 from = "hsla(0 0% 42% 0.66)";
340 to = "hsla(0 0% 35% 0.66)";
341 angle = 29;
342 relative-to = "workspace-view";
343 };
344 };
345 384
346 preset-column-widths = [
347 { proportion = 1. / 4.; }
348 { proportion = 1. / 3.; }
349 { proportion = 1. / 2.; }
350 { proportion = 2. / 3.; }
351 { proportion = 3. / 4.; }
352 ];
353 default-column-width.proportion = 1. / 2.;
354 preset-window-heights = [
355 { proportion = 1. / 3.; }
356 { proportion = 1. / 2.; }
357 { proportion = 2. / 3.; }
358 { proportion = 1.; }
359 ];
360 385
361 always-center-single-column = true; 386 class Niri(socket.socket):
362 }; 387 def __init__(self):
388 super().__init__(socket.AF_UNIX, socket.SOCK_STREAM)
389 super().connect(os.environ["NIRI_SOCKET"])
390 self.fh = super().makefile(mode='rw', buffering=1, encoding='utf-8')
363 391
364 cursor.hide-when-typing = true; 392 def cmd(self, obj):
393 print(json.dumps(obj, separators=(',', ':')), flush=True, file=self.fh)
365 394
366 input = { 395 def event_stream(self):
367 touchpad.enable = false; 396 self.cmd("EventStream")
368 trackball = { 397 return self.fh
369 scroll-method = "on-button-down";
370 scroll-button = 278;
371 };
372 };
373 398
374 workspaces = {
375 "001" = { name = "pwctl"; open-on-output = "eDP-1"; };
376 "002" = { name = "kpxc"; open-on-output = "eDP-1"; };
377 "003" = { name = "bmgr"; open-on-output = "eDP-1"; };
378 "004" = { name = "term"; open-on-output = "eDP-1"; };
379 "005" = { name = "edit"; open-on-output = "eDP-1"; };
380 "006" = { name = "eff"; open-on-output = "eDP-1"; };
381 "101".name = "comm";
382 "102".name = "web";
383 # "104".name = "read";
384 # "105".name = "mon";
385 "110".name = "vid";
386 "120".name = "bmr";
387 };
388 399
389 window-rules = [ 400 with Niri() as niri, Niri().event_stream() as niri_stream:
390 { 401 for line in niri_stream:
391 matches = [ { is-floating = true; } ]; 402 workspaces = None
392 geometry-corner-radius = 403 if line_json := json.loads(line):
393 let 404 if "WorkspacesChanged" in line_json:
394 allCorners = r: { bottom-left = r; bottom-right = r; top-left = r; top-right = r; }; 405 workspaces = line_json["WorkspacesChanged"]["workspaces"]
395 in allCorners 8.;
396 clip-to-geometry = true;
397 }
398 {
399 matches = [ { app-id = "^com\.saivert\.pwvucontrol$"; } ];
400 open-on-workspace = "pwctl";
401 open-maximized = true;
402 }
403 {
404 matches = [ { app-id = "^com\.github\.wwmm\.easyeffects$"; } ];
405 open-on-workspace = "eff";
406 open-maximized = true;
407 }
408 {
409 matches = [ { app-id = "^\.blueman-manager-wrapped$"; } ];
410 open-on-workspace = "bmgr";
411 open-maximized = true;
412 }
413 {
414 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
415 block-out-from = "screencast";
416 }
417 {
418 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
419 excludes = [
420 { title = "^Unlock Database.*"; }
421 { title = "^Access Request.*"; }
422 { title = ".*Passkey credentials$"; }
423 ];
424 open-on-workspace = "kpxc";
425 open-maximized = true;
426 open-focused = false;
427 }
428 {
429 matches = [
430 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Unlock Database.*"; }
431 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Access Request.*"; }
432 { app-id = "^org\.keepassxc\.KeePassXC$"; title = ".*Passkey credentials$"; }
433 ];
434 open-focused = true;
435 open-floating = true;
436 }
437 {
438 matches = [ { app-id = "^kitty-scratch$"; } ];
439 open-on-workspace = "term";
440 open-maximized = true;
441 }
442 {
443 matches = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
444 open-on-workspace = "edit";
445 open-maximized = true;
446 }
447 {
448 matches = [
449 { app-id = "^emacs$"; }
450 { app-id = "^firefox$"; }
451 ];
452 default-column-width.proportion = 2. / 3.;
453 }
454 {
455 matches = [
456 { app-id = "^kitty$"; }
457 { app-id = "^kitty-play$"; }
458 ];
459 default-column-width.proportion = 1. / 3.;
460 }
461 {
462 matches = [
463 { app-id = "^thunderbird$"; }
464 { app-id = "^Element$"; }
465 { app-id = "^Rainbow$"; }
466 ];
467 open-on-workspace = "comm";
468 }
469 {
470 matches = [ { app-id = "^firefox$"; } ];
471 open-on-workspace = "web";
472 open-maximized = true;
473 variable-refresh-rate = true;
474 }
475 # {
476 # matches = [
477 # { app-id = "^evince$"; }
478 # { app-id = "^imv$"; }
479 # { app-id = "^org\.pwmt\.zathura$"; }
480 # ];
481 # open-on-workspace = "read";
482 # }
483 {
484 matches = [ { app-id = "^mpv$"; } ];
485 open-on-workspace = "vid";
486 default-column-width.proportion = 1.;
487 variable-refresh-rate = true;
488 }
489 {
490 matches = [ { app-id = "^kitty-play$"; } ];
491 open-on-workspace = "vid";
492 open-focused = false;
493 }
494 # {
495 # matches = [
496 # { app-id = "^qemu$"; }
497 # { app-id = "^virt-manager$"; }
498 # ];
499 # open-on-workspace = "mon";
500 # }
501 {
502 matches = [ { app-id = "^pdfpc$"; } ];
503 default-column-width.proportion = 1.;
504 }
505 {
506 matches = [ { app-id = "^pdfpc$"; title = "^pdfpc - presentation"; } ];
507 open-on-workspace = "bmr";
508 open-fullscreen = true;
509 }
510 {
511 matches = [
512 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
513 { app-id = "^org\.kde\.polkit-kde-authentication-agent-1$"; }
514 { app-id = "^xdg-desktop-portal-gtk$"; }
515 ];
516 open-floating = true;
517 }
518 ];
519 layer-rules = [
520 { matches = [
521 { namespace = "^notifications$"; }
522 { namespace = "^waybar$"; }
523 ];
524 block-out-from = "screencast";
525 }
526 ];
527
528 binds = with config.lib.niri.actions; {
529 "Mod+Slash".action = show-hotkey-overlay;
530
531 "Mod+Return".action = spawn terminal;
532 "Mod+Q".action = close-window;
533 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
534 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
535
536 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
537 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
538 name = "queue-yt-dlp";
539 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
540 text = ''
541 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
542 '';
543 }));
544 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
545 name = "queue-yt-dlp";
546 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
547 text = ''
548 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
549 '';
550 }));
551
552 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
553 name = "qalc-fuzzel";
554 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
555 text = ''
556 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
557 prev() {
558 FOUND=false
559 while IFS= read -r line; do
560 [[ -n "$line" ]] || continue
561 FOUND=true
562 echo "$line"
563 done < <(export LC_ALL=C.UTF-8; echo; find "$RESULTS_DIR" -type f -printf $'%T@ %p\n' | sort -n | cut -d' ' -f2- | xargs -r cat)
564 $FOUND || echo
565 }
566 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
567 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
568 QALC_RES="$FUZZEL_RES"
569 QALC_RET=0
570 else
571 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
572 QALC_RET=$?
573 fi
574 [[ -n "$QALC_RES" ]] || exit 1
575 EXISTING=false
576 set +o pipefail
577 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
578 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
579 set -o pipefail
580 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
581 set +o pipefail
582 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
583 set -o pipefail
584 cat >"$RES_FILE" <<<"$QALC_RES"
585 fi
586 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
587 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
588 notify-send "$QALC_RES"
589 '';
590 }));
591 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
592 name = "emoji-fuzzel";
593 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
594 text = ''
595 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
596 [[ -n "$FUZZEL_RES" ]] || exit 1
597 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
598 '';
599 }));
600 "Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
601 name = "screenshot";
602 runtimeInputs = with pkgs; [ grim slurp wl-clipboard-rs coreutils ];
603 text = ''
604 grim -g "$(slurp -b 00000080 -c FFFFFFFF -s 00000000 -w 1)" - \
605 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
606 | wl-copy --type image/png
607 '';
608 }));
609 "Shift+Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
610 name = "screenshot";
611 runtimeInputs = with pkgs; [ grim niri gojq wl-clipboard-rs coreutils ];
612 text = ''
613 grim -o "$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .output')" - \
614 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
615 | wl-copy --type image/png
616 '';
617 }));
618 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
619 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
620
621 "Mod+H".action = focus-column-left;
622 "Mod+T".action = focus-window-down;
623 "Mod+N".action = focus-window-up;
624 "Mod+S".action = focus-column-right;
625
626 "Mod+Shift+H".action = move-column-left;
627 "Mod+Shift+T".action = move-window-down;
628 "Mod+Shift+N".action = move-window-up;
629 "Mod+Shift+S".action = move-column-right;
630
631 "Mod+Control+H".action = focus-monitor-left;
632 "Mod+Control+T".action = focus-monitor-down;
633 "Mod+Control+N".action = focus-monitor-up;
634 "Mod+Control+S".action = focus-monitor-right;
635
636 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
637 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
638 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
639 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
640
641 "Mod+G".action = focus-adjacent-workspace "down";
642 "Mod+C".action = focus-adjacent-workspace "up";
643
644 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
645 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
646
647 "Mod+Shift+Control+G".action = move-workspace-down;
648 "Mod+Shift+Control+C".action = move-workspace-up;
649
650 "Mod+ParenLeft".action = focus-workspace "comm";
651 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
652
653 "Mod+ParenRight".action = focus-workspace "web";
654 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
655
656 "Mod+BraceRight".action = focus-workspace "read";
657 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
658
659 "Mod+BraceLeft".action = focus-workspace "mon";
660 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
661
662 "Mod+Asterisk".action = focus-workspace "vid";
663 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
664
665 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
666 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
667
668 "Mod+M".action = consume-or-expel-window-left;
669 "Mod+W".action = consume-or-expel-window-right;
670
671 "Mod+R".action = switch-preset-column-width;
672 "Mod+Shift+R".action = switch-preset-window-height;
673 "Mod+F".action = center-column;
674 "Mod+Shift+F".action = maximize-column;
675 "Mod+Shift+Ctrl+F".action = fullscreen-window;
676
677 "Mod+V".action = switch-focus-between-floating-and-tiling;
678 "Mod+Shift+V".action = toggle-window-floating;
679
680 "Mod+Left".action = set-column-width "-10%";
681 "Mod+Down".action = set-window-height "-10%";
682 "Mod+Up".action = set-window-height "+10%";
683 "Mod+Right".action = set-column-width "+10%";
684
685 "Mod+Shift+Z" = {
686 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
687 allow-when-locked = true;
688 };
689 "Mod+Shift+L".action = spawn loginctl "lock-session";
690 "Mod+Shift+E".action = quit;
691 "Mod+Shift+Minus" = {
692 action = spawn systemctl "suspend";
693 allow-when-locked = true;
694 };
695 "Mod+Shift+Control+Minus" = {
696 action = spawn systemctl "hibernate";
697 allow-when-locked = true;
698 };
699 "Mod+Shift+P" = {
700 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
701 allow-when-locked = true;
702 };
703
704 "XF86MonBrightnessUp" = {
705 action = spawn swayosd-client "--brightness" "raise";
706 allow-when-locked = true;
707 };
708 "XF86MonBrightnessDown" = {
709 action = spawn swayosd-client "--brightness" "lower";
710 allow-when-locked = true;
711 };
712 "XF86AudioRaiseVolume" = {
713 action = spawn swayosd-client "--output-volume" "raise";
714 allow-when-locked = true;
715 };
716 "XF86AudioLowerVolume" = {
717 action = spawn swayosd-client "--output-volume" "lower";
718 allow-when-locked = true;
719 };
720 "XF86AudioMute" = {
721 action = spawn swayosd-client "--output-volume" "mute-toggle";
722 allow-when-locked = true;
723 };
724 "XF86AudioMicMute" = {
725 action = spawn swayosd-client "--input-volume" "mute-toggle";
726 allow-when-locked = true;
727 };
728 406
729 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group"; 407 if workspaces is None:
730 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all"; 408 continue
731 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu"; 409
732 "Mod+Comma".action = spawn makoctl "restore"; 410 old_outputs = outputs
733 411 outputs = {ws["output"] for ws in workspaces}
734 "Mod+Control+A".action = focus-or-spawn-action-app_id "com.saivert.pwvucontrol" "pwctl" "pwvucontrol"; 412 if old_outputs is None:
735 "Mod+Control+O".action = focus-or-spawn-action-app_id "com.github.wwmm.easyeffects" "eff" "easyeffects"; 413 print("Initial outputs: {}".format(outputs), file=sys.stderr)
736 "Mod+Control+P".action = focus-or-spawn-action-app_id "org.keepassxc.KeePassXC" "kpxc" "keepassxc"; 414 continue
737 "Mod+Control+B".action = focus-or-spawn-action-app_id ".blueman-manager-wrapped" "bmgr" "blueman-manager"; 415
738 "Mod+Control+Return".action = focus-or-spawn-action-app_id "kitty-scratch" "term" "kitty" "--app-id" "kitty-scratch"; 416 new_outputs = outputs - old_outputs
739 "Mod+Control+E".action = focus-or-spawn-action "select(.app_id == \"emacs\" and .title == \"scratch\")" "edit" "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))"; 417 if not new_outputs:
418 continue
419 print("New outputs: {}".format(new_outputs), file=sys.stderr)
420
421 relevant_workspaces = list(filter(lambda ws: (ws["name"] is not None) or (ws["active_window_id"] is not None), workspaces))
422 target_output = next(iter(outputs - set(only.keys())))
423 if not target_output:
424 continue
425 for ws in relevant_workspaces:
426 ws_ident = ws["name"] if ws["name"] is not None else (ws["output"], ws["idx"])
427 if ws["output"] not in set(only.keys()):
428 continue
429 if ws_ident in only[ws["output"]]:
430 continue
431
432 print("{} -> {}".format(ws_ident, target_output), file=sys.stderr)
433 niri.cmd({"Action": {"MoveWorkspaceToMonitor": {"reference": {"Id": ws["id"]}, "output": target_output}}})
434 '';
435 Restart = "on-failure";
436 RestartSec = 10;
740 }; 437 };
741 }; 438 };
439
440 programs.niri.scratchspaces = [
441 { name = "pwctl";
442 key = "Mod+Control+A";
443 spawn = ["pwvucontrol"];
444 app-id = "com.saivert.pwvucontrol";
445 }
446 { name = "kpxc";
447 exclude = [
448 { title = "^Unlock Database.*"; }
449 { title = "^Access Request.*"; }
450 { title = ".*Passkey credentials$"; }
451 ];
452 windowRuleExtra = with kdl; [
453 (kdl.leaf "open-focused" false)
454 ];
455 key = "Mod+Control+P";
456 app-id = "org.keepassxc.KeePassXC";
457 spawn = [ "keepassxc" ];
458 }
459 { name = "bmgr";
460 key = "Mod+Control+B";
461 app-id = ".blueman-manager-wrapped";
462 spawn = [ "blueman-manager" ];
463 }
464 { name = "term";
465 key = "Mod+Control+Return";
466 app-id = "kitty-scratch";
467 spawn = [ "kitty" "--app-id" "kitty-scratch" ];
468 }
469 { name = "edit";
470 match = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
471 key = "Mod+Control+E";
472 selector = "select(.app_id == \"emacs\" and .title == \"scratch\")";
473 spawn = [ "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))" ];
474 }
475 { name = "eff";
476 key = "Mod+Control+O";
477 app-id = "com.github.wwmm.easyeffects";
478 spawn = [ "easyeffects" ];
479 }
480 { name = "time";
481 key = "Mod+Control+K";
482 app-id = "chrome-kimai.yggdrasil.li__-Default";
483 spawn = [ (toString (pkgs.resholve.writeScript "kimai" {
484 interpreter = pkgs.runtimeShell;
485 inputs = [ pkgs.dex ];
486 execer = [ "cannot:${lib.getExe pkgs.dex}" ];
487 } ''
488 exec dex $HOME/.local/state/nix/profile/share/applications/kimai.desktop
489 '')) ];
490 windowRuleExtra = with kdl; [
491 (leaf "block-out-from" "screencast")
492 ];
493 }
494 ];
495 programs.niri.config =
496 let
497 inherit (kdl) node plain leaf flag;
498 optional-node = cond: v:
499 if cond
500 then v
501 else null;
502 opt-props = lib.filterAttrs (lib.const (value: value != null));
503 in
504 [ (flag "prefer-no-csd")
505
506 (leaf "screenshot-path" "~/screenshots/%Y-%m-%dT%H:%M:%S.png")
507
508 (plain "hotkey-overlay" [
509 (flag "skip-at-startup")
510 ])
511
512 (plain "input" [
513 (plain "keyboard" [
514 (leaf "repeat-delay" 300)
515 (leaf "repeat-rate" 50)
516
517 (plain "xkb" [
518 (leaf "layout" "us,us")
519 (leaf "variant" "dvp,")
520 (leaf "options" "compose:caps,grp:win_space_toggle")
521 ])
522 ])
523
524 (flag "workspace-auto-back-and-forth")
525 # (leaf "focus-follows-mouse" {})
526 # (flag "warp-mouse-to-focus")
527
528 # (plain "touchpad" [ (flag "off") ])
529 (plain "trackball" [
530 (leaf "scroll-method" "on-button-down")
531 (leaf "scroll-button" 278)
532 ])
533 (plain "touch" [
534 (leaf "map-to-output" "eDP-1")
535 ])
536 ])
537
538 (plain "gestures" [
539 (plain "hot-corners" [(flag "off")])
540 ])
541
542 (plain "environment" (lib.mapAttrsToList leaf {
543 NIXOS_OZONE_WL = "1";
544 QT_QPA_PLATFORM = "wayland";
545 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
546 GDK_BACKEND = "wayland";
547 SDL_VIDEODRIVER = "wayland";
548 DISPLAY = ":0";
549 ELECTRON_OZONE_PLATFORM_HINT = "auto";
550 SSH_ASKPASS_REQUIRE = "prefer";
551 SSH_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
552 SUDO_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
553 }))
554
555 (node "output" "eDP-1" [
556 (leaf "scale" 1.5)
557 (leaf "position" { x = 0; y = 0; })
558 ])
559 (node "output" "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" [
560 (leaf "scale" 1.5)
561 (leaf "position" { x = 2560; y = 0; })
562 ])
563 (node "output" "HP Inc. HP 727pu CN4417143K" [
564 (leaf "mode" "2560x1440@119.998")
565 (leaf "scale" 1)
566 (leaf "position" { x = 2560; y = 0; })
567 (flag "variable-refresh-rate")
568 ])
569
570 (plain "debug" [
571 (leaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render")
572 ])
573
574 (plain "animations" [
575 (leaf "slowdown" 0.5)
576 (plain "workspace-switch" [(flag "off")])
577 ])
578
579 (plain "layout" [
580 (leaf "gaps" 8)
581 (plain "struts" [
582 (leaf "left" 26)
583 (leaf "right" 26)
584 (leaf "top" 0)
585 (leaf "bottom" 0)
586 ])
587 (plain "border" [
588 (leaf "width" 2)
589 (leaf "active-gradient" {
590 from = "hsla(195 100% 45% 1)";
591 to = "hsla(155 100% 37.5% 1)";
592 angle = 29;
593 relative-to = "workspace-view";
594 })
595 (leaf "inactive-gradient" {
596 from = "hsla(0 0% 27.7% 1)";
597 to = "hsla(0 0% 23% 1)";
598 angle = 29;
599 relative-to = "workspace-view";
600 })
601 ])
602 (plain "focus-ring" [
603 (flag "off")
604 ])
605
606 (plain "preset-column-widths" (map (prop: leaf "proportion" prop) [
607 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.)
608 ]))
609 (plain "default-column-width" [ (leaf "proportion" (1. / 2.)) ])
610 (plain "preset-window-heights" (map (prop: leaf "proportion" prop) [
611 (1. / 3.) (1. / 2.) (2. / 3.) (1.)
612 ]))
613
614 (flag "always-center-single-column")
615
616 (plain "tab-indicator" [
617 (leaf "gap" 4)
618 (leaf "width" 8)
619 (leaf "gaps-between-tabs" 4)
620 (flag "place-within-column")
621 (leaf "length" { total-proportion = 1.; })
622 (leaf "active-gradient" {
623 from = "hsla(195 100% 60% 0.75)";
624 to = "hsla(155 100% 50% 0.75)";
625 angle = 29;
626 relative-to = "workspace-view";
627 })
628 (leaf "inactive-gradient" {
629 from = "hsla(0 0% 42% 0.66)";
630 to = "hsla(0 0% 35% 0.66)";
631 angle = 29;
632 relative-to = "workspace-view";
633 })
634 ])
635 ])
636
637 (plain "cursor" [
638 (flag "hide-when-typing")
639 ])
640
641 (map (name:
642 (node "workspace" name [
643 (leaf "open-on-output" "eDP-1")
644 ])
645 ) (map ({name, ...}: name) cfg.scratchspaces))
646 (map (name:
647 (leaf "workspace" name)
648 ) ["comm" "web" "vid" "bmr"])
649
650 (plain "window-rule" [
651 (leaf "clip-to-geometry" true)
652 ])
653
654 (plain "window-rule" [
655 (leaf "match" { is-floating = true; })
656 (leaf "geometry-corner-radius" 8)
657 (plain "shadow" [ (flag "on") ])
658 ])
659
660 (plain "window-rule" [
661 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; })
662 (leaf "block-out-from" "screencast")
663 ])
664 (plain "window-rule" [
665 (map (title:
666 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; })
667 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$" "Browser Access Request$"])
668 (leaf "open-focused" true)
669 (leaf "open-floating" true)
670 ])
671
672 (map ({ name, match, exclude, windowRuleExtra, ... }:
673 (optional-node (match != []) (plain "window-rule" [
674 (map (leaf "match") match)
675 (map (leaf "exclude") exclude)
676 (leaf "open-on-workspace" name)
677 (leaf "open-maximized" true)
678 windowRuleExtra
679 ]))
680 ) cfg.scratchspaces)
681
682 (plain "window-rule" [
683 (leaf "match" { app-id = "^emacs$"; })
684 (leaf "match" { app-id = "^firefox$"; })
685 (plain "default-column-width" [(leaf "proportion" (2. / 3.))])
686 ])
687 (plain "window-rule" [
688 (leaf "match" { app-id = "^kitty$"; })
689 (leaf "match" { app-id = "^kitty-play$"; })
690 (plain "default-column-width" [(leaf "proportion" (1. / 3.))])
691 ])
692
693 (plain "window-rule" [
694 (leaf "match" { app-id = "^thunderbird$"; })
695 (leaf "match" { app-id = "^Element$"; })
696 (leaf "match" { app-id = "^chrome-web\.openrainbow\.com__-Default$"; })
697 (leaf "open-on-workspace" "comm")
698 ])
699 (plain "window-rule" [
700 (leaf "match" { app-id = "^firefox$"; })
701 (leaf "open-on-workspace" "web")
702 (leaf "open-maximized" true)
703 ])
704 (plain "window-rule" [
705 (leaf "match" { app-id = "^mpv$"; })
706 (leaf "open-on-workspace" "vid")
707 (plain "default-column-width" [(leaf "proportion" 1.)])
708 ])
709 (plain "window-rule" [
710 (leaf "match" { app-id = "^kitty-play$"; })
711 (leaf "open-on-workspace" "vid")
712 (leaf "open-focused" false)
713 ])
714 (plain "window-rule" [
715 (leaf "match" { app-id = "^chrome-audiobookshelf\.yggdrasil\.li__-Default$"; })
716 (leaf "match" { app-id = "^YouTube Music Desktop App$"; })
717 (leaf "open-on-workspace" "vid")
718 ])
719 (plain "window-rule" [
720 (leaf "match" { app-id = "^pdfpc$"; })
721 (plain "default-column-width" [(leaf "proportion" 1.)])
722 ])
723 (plain "window-rule" [
724 (leaf "match" { app-id = "^pdfpc$"; title = "^.*presentation.*$"; })
725 (plain "default-column-width" [(leaf "proportion" 1.)])
726 (leaf "open-fullscreen" true)
727 (leaf "open-on-workspace" "bmr")
728 (leaf "open-focused" false)
729 ])
730 (plain "window-rule" [
731 (map (leaf "match") [
732 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
733 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; }
734 { app-id = "^xdg-desktop-portal-gtk$"; }
735 ])
736 (leaf "open-floating" true)
737 ])
738 (plain "window-rule" [
739 (leaf "match" { app-id = "^org\\.pwmt\\.zathura$"; })
740 (leaf "match" { app-id = "^evince$"; })
741 (leaf "match" { app-id = "^org\\.gnome\\.Papers$"; })
742 (leaf "default-column-display" "tabbed")
743 ])
744
745 (plain "layer-rule" [
746 (leaf "match" { namespace = "^notifications$"; })
747 (leaf "match" { namespace = "^waybar$"; })
748 (leaf "match" { namespace = "^launcher$"; })
749 (leaf "block-out-from" "screencast")
750 ])
751
752 (plain "binds"
753 (let
754 bind = name: cfg: node name (opt-props {
755 cooldown-ms = cfg.cooldown-ms or null;
756 }
757 // (lib.optionalAttrs (!(cfg.repeat or true)) {
758 repeat = false;
759 })
760 // (lib.optionalAttrs (cfg.allow-when-locked or false) {
761 allow-when-locked = true;
762 })) (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
763 in
764 [
765 (lib.mapAttrsToList bind (with config.lib.niri.actions; {
766 "Mod+Slash".action = show-hotkey-overlay;
767
768 "Mod+Return".action = spawn terminal;
769 "Mod+Shift+Return".action =
770 let
771 nushellKitty = pkgs.symlinkJoin {
772 name = "nushell-kitty";
773 paths = [ config.programs.kitty.package ];
774 buildInputs = [ pkgs.makeWrapper ];
775 postBuild = ''
776 wrapProgram $out/bin/kitty \
777 --add-flags "--config ${pkgs.writeText "kitty.conf" ''
778 include $HOME/${config.xdg.configFile."kitty/kitty.conf".target}
779 shell ${lib.getExe config.programs.nushell.package}
780 ''}"
781 '';
782 };
783 in spawn (lib.getExe' nushellKitty "kitty");
784 "Mod+Q".action = close-window;
785 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
786 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
787
788 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
789 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
790 name = "queue-yt-dlp";
791 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
792 text = ''
793 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
794 '';
795 }));
796 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
797 name = "queue-yt-dlp";
798 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
799 text = ''
800 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
801 '';
802 }));
803 "Mod+Alt+M".action = spawn (lib.getExe' pkgs.screen-message "sm") "-n" "Fira Mono" "-a" "1" "-f" "#fff" "-b" "#000";
804
805 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
806 name = "qalc-fuzzel";
807 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
808 text = ''
809 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
810 prev() {
811 FOUND=false
812 while IFS= read -r line; do
813 [[ -n "$line" ]] || continue
814 FOUND=true
815 echo "$line"
816 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)
817 $FOUND || echo
818 }
819 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> " --width=60) || exit $?
820 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
821 QALC_RES="$FUZZEL_RES"
822 QALC_RET=0
823 else
824 QALC_RES=$(qalc -set "autocalc off" "$FUZZEL_RES" 2>&1)
825 QALC_RET=$?
826 fi
827 [[ -n "$QALC_RES" ]] || exit 1
828 EXISTING=false
829 set +o pipefail
830 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
831 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
832 set -o pipefail
833 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
834 set +o pipefail
835 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
836 set -o pipefail
837 cat >"$RES_FILE" <<<"$QALC_RES"
838 fi
839 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
840 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
841 notify-send "$QALC_RES"
842 '';
843 }));
844 "Mod+Shift+U".action =
845 let
846 qalcKitty = pkgs.symlinkJoin {
847 name = "qalc-kitty";
848 paths = [ config.programs.kitty.package ];
849 buildInputs = [ pkgs.makeWrapper ];
850 postBuild = ''
851 wrapProgram $out/bin/kitty \
852 --add-flags "--config ${pkgs.writeText "kitty.conf" ''
853 include $HOME/${config.xdg.configFile."kitty/kitty.conf".target}
854 shell ${lib.getExe pkgs.libqalculate}
855 ''}"
856 '';
857 };
858 in spawn (lib.getExe' qalcKitty "kitty");
859 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
860 name = "emoji-fuzzel";
861 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
862 text = ''
863 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " --cache "$HOME"/.cache/fuzzel-emoji --width=60 <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
864 [[ -n "$FUZZEL_RES" ]] || exit 1
865 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
866 '';
867 }));
868 "Print".action = screenshot;
869 "Control+Print".action = screenshot-window;
870 "Shift+Print".action = kdl.magic-leaf "screenshot-screen";
871 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
872 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
873
874 "Mod+Escape" = {
875 allow-inhibiting = false;
876 action = toggle-keyboard-shortcuts-inhibit;
877 };
878
879 "Mod+H".action = focus-column-left;
880 "Mod+T".action = focus-window-down;
881 "Mod+N".action = focus-window-up;
882 "Mod+S".action = focus-column-right;
883
884 "Mod+Shift+H".action = move-column-left;
885 "Mod+Shift+T".action = move-window-down;
886 "Mod+Shift+N".action = move-window-up;
887 "Mod+Shift+S".action = move-column-right;
888
889 "Mod+Control+H".action = focus-monitor-left;
890 "Mod+Control+T".action = focus-monitor-down;
891 "Mod+Control+N".action = focus-monitor-up;
892 "Mod+Control+S".action = focus-monitor-right;
893
894 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
895 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
896 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
897 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
898
899 "Mod+G".action = focus-adjacent-workspace "down";
900 "Mod+C".action = focus-adjacent-workspace "up";
901
902 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
903 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
904
905 "Mod+Shift+Control+G".action = move-workspace-down;
906 "Mod+Shift+Control+C".action = move-workspace-up;
907
908 "Mod+ParenLeft".action = focus-workspace "comm";
909 "Mod+Shift+ParenLeft".action = kdl.magic-leaf "move-column-to-workspace" "comm";
910
911 "Mod+ParenRight".action = focus-workspace "web";
912 "Mod+Shift+ParenRight".action = kdl.magic-leaf "move-column-to-workspace" "web";
913
914 "Mod+BraceRight".action = focus-workspace "read";
915 "Mod+Shift+BraceRight".action = kdl.magic-leaf "move-column-to-workspace" "read";
916
917 "Mod+BraceLeft".action = focus-workspace "mon";
918 "Mod+Shift+BraceLeft".action = kdl.magic-leaf "move-column-to-workspace" "mon";
919
920 "Mod+Asterisk".action = focus-workspace "vid";
921 "Mod+Shift+Asterisk".action = kdl.magic-leaf "move-column-to-workspace" "vid";
922
923 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
924 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
925
926 "Mod+M".action = consume-or-expel-window-left;
927 "Mod+W".action = consume-or-expel-window-right;
928
929 "Mod+Shift+M".action = toggle-column-tabbed-display;
930
931 "Mod+R".action = switch-preset-column-width;
932 "Mod+Shift+R".action = maximize-column;
933 "Mod+Shift+Ctrl+R".action = switch-preset-window-height;
934 "Mod+F".action = center-column;
935 "Mod+Shift+F".action = toggle-windowed-fullscreen;
936 "Mod+Ctrl+Shift+F".action = fullscreen-window;
937
938 "Mod+V".action = switch-focus-between-floating-and-tiling;
939 "Mod+Shift+V".action = toggle-window-floating;
940
941 "Mod+Left".action = set-column-width "-10%";
942 "Mod+Down".action = set-window-height "-10%";
943 "Mod+Up".action = set-window-height "+10%";
944 "Mod+Right".action = set-column-width "+10%";
945
946 "Mod+Shift+Z" = {
947 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
948 allow-when-locked = true;
949 };
950 "Mod+Shift+L".action = spawn loginctl "lock-session";
951 "Mod+Shift+E".action = quit;
952 "Mod+Shift+Minus" = {
953 action = spawn systemctl "suspend";
954 allow-when-locked = true;
955 };
956 "Mod+Shift+Control+Minus" = {
957 action = spawn systemctl "hibernate";
958 allow-when-locked = true;
959 };
960 "Mod+Shift+P" = {
961 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
962 allow-when-locked = true;
963 };
964
965 "XF86MonBrightnessUp" = {
966 action = spawn swayosd-client "--brightness" "raise";
967 allow-when-locked = true;
968 };
969 "XF86MonBrightnessDown" = {
970 action = spawn swayosd-client "--brightness" "lower";
971 allow-when-locked = true;
972 };
973 "XF86AudioRaiseVolume" = {
974 action = spawn swayosd-client "--output-volume" "raise";
975 allow-when-locked = true;
976 };
977 "XF86AudioLowerVolume" = {
978 action = spawn swayosd-client "--output-volume" "lower";
979 allow-when-locked = true;
980 };
981 "XF86AudioMute" = {
982 action = spawn swayosd-client "--output-volume" "mute-toggle";
983 allow-when-locked = true;
984 };
985 "XF86AudioMicMute" = {
986 action = spawn swayosd-client "--input-volume" "mute-toggle";
987 allow-when-locked = true;
988 };
989
990 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
991 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
992 "Mod+Period".action = spawn makoctl "menu" "--" (lib.getExe config.programs.fuzzel.package) "--dmenu";
993 "Mod+Comma".action = spawn makoctl "restore";
994
995 "Mod+Control+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"FocusWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
996 "Mod+Control+Shift+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"MoveColumnToWorkspace\":{\"reference\":{\"Id\": $workspace_id}, \"focus\": true}}}";
997
998 "Mod+X".action = set-dynamic-cast-window;
999 "Mod+Shift+X".action = set-dynamic-cast-monitor;
1000 "Mod+Control+Shift+X".action = clear-dynamic-cast-target;
1001
1002 "Mod+D".action = with-urgent-window-action "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
1003 "Mod+Shift+D".action = with-focused-window-action "{\"Action\":{\"UnsetUrgent\":{\"id\": .id}}}";
1004
1005 "Mod+K".action = spawn (lib.getExe' pkgs.worktime "worktime-ui");
1006 "Mod+Shift+K".action = spawn (lib.getExe' pkgs.worktime "worktime-stop");
1007 }))
1008 (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)
1009 (map ({ name, moveKey, ...}: if moveKey != null then bind moveKey { action = kdl.magic-leaf "move-column-to-workspace" name; } else null) cfg.scratchspaces)
1010 ]
1011 ))
1012 ];
742 }; 1013 };
743} 1014}
diff --git a/accounts/gkleen@sif/niri/mako.nix b/accounts/gkleen@sif/niri/mako.nix
index 0a10555a..eba26caa 100644
--- a/accounts/gkleen@sif/niri/mako.nix
+++ b/accounts/gkleen@sif/niri/mako.nix
@@ -3,33 +3,31 @@
3 config = { 3 config = {
4 services.mako = { 4 services.mako = {
5 enable = true; 5 enable = true;
6 font = "Fira Sans 10"; 6 settings = {
7 format = "<i>%s</i>\\n%b"; 7 font = "Fira Sans 10";
8 margin = "2"; 8 format = "<i>%s</i>\\n%b";
9 maxVisible = -1; 9 margin = "2";
10 backgroundColor = "#000000dd"; 10 max-visible = -1;
11 progressColor = "source #223544ff"; 11 background-color = "#000000dd";
12 width = 384; 12 progress-color = "source #223544ff";
13 extraConfig = '' 13 width = 384;
14 outer-margin=1 14 outer-margin = 1;
15 max-history=100 15 max-history = 100;
16 max-icon-size=48 16 max-icon-size = 48;
17 17 };
18 [grouped] 18 criteria = {
19 format=<b>(%g)</b> <i>%s</i>\n%b 19 grouped.format = "<b>(%g)</b> <i>%s</i>\\n%b";
20 20 "urgency=low".text-color = "#999999ff";
21 [urgency=low] 21 "urgency=critical".background-color = "#900000dd";
22 text-color=#999999ff 22 "app-name=Element".group-by = "summary";
23 23 "app-name=poweralertd" = {
24 [urgency=critical] 24 history = false;
25 background-color=#900000dd 25 ignore-timeout = true;
26 26 default-timeout = 2000;
27 [app-name=Element] 27 };
28 group-by=summary 28 "app-name=worktime".history = false;
29 29 "mode=silent".invisible = true;
30 [mode=silent] 30 };
31 invisible=1
32 '';
33 package = pkgs.symlinkJoin { 31 package = pkgs.symlinkJoin {
34 name = "${pkgs.mako.name}-wrapped"; 32 name = "${pkgs.mako.name}-wrapped";
35 paths = with pkgs; [ mako ]; 33 paths = with pkgs; [ mako ];
diff --git a/accounts/gkleen@sif/niri/swayosd.nix b/accounts/gkleen@sif/niri/swayosd.nix
index 984927c2..54ebb302 100644
--- a/accounts/gkleen@sif/niri/swayosd.nix
+++ b/accounts/gkleen@sif/niri/swayosd.nix
@@ -3,9 +3,10 @@
3 config = { 3 config = {
4 services.swayosd = { 4 services.swayosd = {
5 enable = true; 5 enable = true;
6 topMargin = 0.946154; 6 topMargin = 0.4769706078;
7 stylePath = pkgs.runCommand "style.css" { 7 stylePath = pkgs.runCommand "style.css" {
8 src = pkgs.writeText "style.scss" '' 8 passAsFile = [ "src" ];
9 src = ''
9 window#osd { 10 window#osd {
10 padding: 12px 20px; 11 padding: 12px 20px;
11 border-radius: 999px; 12 border-radius: 999px;
@@ -59,7 +60,7 @@
59 } 60 }
60 ''; 61 '';
61 buildInputs = with pkgs; [sass]; 62 buildInputs = with pkgs; [sass];
62 } "scss -C --sourcemap=none --style=compact $src $out"; 63 } "scss -C --sourcemap=none --style=compact $srcPath $out";
63 }; 64 };
64 }; 65 };
65} 66}
diff --git a/accounts/gkleen@sif/niri/waybar.nix b/accounts/gkleen@sif/niri/waybar.nix
index 3f1f8119..c02a9a76 100644
--- a/accounts/gkleen@sif/niri/waybar.nix
+++ b/accounts/gkleen@sif/niri/waybar.nix
@@ -20,15 +20,21 @@ in {
20 { 20 {
21 layer = "top"; 21 layer = "top";
22 position = "top"; 22 position = "top";
23 height = 14; 23 height = 21;
24 output = [ "eDP-1" "DP-2" "DP-3" ]; 24 output = [ "eDP-1" "DP-2" "DP-3" ];
25 modules-left = [ "niri/workspaces" ]; 25 modules-left = [ "niri/workspaces" ];
26 modules-center = [ "niri/window" ]; 26 modules-center = [ "niri/window" ];
27 modules-right = [ "custom/worktime" "custom/worktime-today" 27 modules-right = [ "custom/worktime" "custom/worktime-today"
28 "custom/weather" 28 "custom/weather"
29 "custom/keymap" 29 "custom/keymap"
30 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "clock" ]; 30 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "custom/lid_inhibitor" "clock" ];
31 31
32 "custom/lid_inhibitor" = {
33 format = "{}";
34 return-type = "json";
35 exec = lib.getExe pkgs.waybar-systemd-inhibit;
36 on-click = lib.getExe' pkgs.waybar-systemd-inhibit "waybar-systemd-inhibit-toggle";
37 };
32 "custom/mako" = { 38 "custom/mako" = {
33 format = "{}"; 39 format = "{}";
34 return-type = "json"; 40 return-type = "json";
@@ -131,7 +137,7 @@ in {
131 return-type = "json"; 137 return-type = "json";
132 }; 138 };
133 "niri/workspaces" = { 139 "niri/workspaces" = {
134 ignore = ["eff" "pwctl" "kpxc" "bmgr" "edit" "term"]; 140 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
135 }; 141 };
136 "niri/window" = { 142 "niri/window" = {
137 separate-outputs = true; 143 separate-outputs = true;
@@ -211,13 +217,13 @@ in {
211 layer = "top"; 217 layer = "top";
212 position = "top"; 218 position = "top";
213 height = 14; 219 height = 14;
214 output = [ "!eDP-1" "!DP-2" "!DP-3" ]; 220 output = [ "!eDP-1" "!DP-2" "!DP-3" "*" ];
215 modules-left = [ "niri/workspaces" ]; 221 modules-left = [ "niri/workspaces" ];
216 modules-center = [ "niri/window" ]; 222 modules-center = [ "niri/window" ];
217 modules-right = [ "clock" ]; 223 modules-right = [ "clock" ];
218 224
219 "niri/workspaces" = { 225 "niri/workspaces" = {
220 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"]; 226 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
221 }; 227 };
222 "niri/window" = { 228 "niri/window" = {
223 separate-outputs = true; 229 separate-outputs = true;
@@ -254,10 +260,10 @@ in {
254 } 260 }
255 261
256 .modules-left { 262 .modules-left {
257 margin-left: 12px; 263 margin-left: 38px;
258 } 264 }
259 .modules-right { 265 .modules-right {
260 margin-right: 12px; 266 margin-right: 38px;
261 } 267 }
262 268
263 .module { 269 .module {
@@ -293,7 +299,7 @@ in {
293 #tray { 299 #tray {
294 margin: 0; 300 margin: 0;
295 } 301 }
296 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako { 302 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako, #custom-lid_inhibitor {
297 color: @grey; 303 color: @grey;
298 margin: 0 5px 0 2px; 304 margin: 0 5px 0 2px;
299 } 305 }
@@ -302,7 +308,11 @@ in {
302 margin-left: 6px; 308 margin-left: 6px;
303 } 309 }
304 #custom-mako { 310 #custom-mako {
305 margin-right: 2px; 311 margin-right: 4px;
312 margin-left: 3px;
313 }
314 #custom-lid_inhibitor {
315 margin-right: 3px;
306 margin-left: 3px; 316 margin-left: 3px;
307 } 317 }
308 #battery { 318 #battery {
@@ -330,7 +340,7 @@ in {
330 color: @orange; 340 color: @orange;
331 } 341 }
332 342
333 #idle_inhibitor { 343 #idle_inhibitor, #custom-lid_inhibitor {
334 padding-top: 1px; 344 padding-top: 1px;
335 } 345 }
336 346
@@ -340,6 +350,7 @@ in {
340 } 350 }
341 #clock { 351 #clock {
342 /* margin-right: 5px; */ 352 /* margin-right: 5px; */
353 font-feature-settings: "tnum";
343 } 354 }
344 ''; 355 '';
345 }; 356 };