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