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.nix576
-rw-r--r--accounts/gkleen@sif/niri/mako.nix115
-rw-r--r--accounts/gkleen@sif/niri/swayosd.nix65
-rw-r--r--accounts/gkleen@sif/niri/waybar.nix137
4 files changed, 796 insertions, 97 deletions
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
index 6aa4391c..7e187c84 100644
--- a/accounts/gkleen@sif/niri/default.nix
+++ b/accounts/gkleen@sif/niri/default.nix
@@ -2,31 +2,37 @@
2let 2let
3 niri = config.programs.niri.package; 3 niri = config.programs.niri.package;
4 terminal = lib.getExe config.programs.kitty.package; 4 terminal = lib.getExe config.programs.kitty.package;
5 lightctl = lib.getExe' config.services.avizo.package "lightctl"; 5 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"; 6 loginctl = lib.getExe' hostConfig.systemd.package "loginctl";
9 systemctl = lib.getExe' hostConfig.systemd.package "systemctl"; 7 systemctl = lib.getExe' hostConfig.systemd.package "systemctl";
8 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
10 9
11 focus-or-spawn = pkgs.writeShellApplication { 10 focus_or_spawn = pkgs.writeShellApplication {
12 name = "focus-or-spawn"; 11 name = "focus-or-spawn";
13 runtimeInputs = [ niri pkgs.gojq pkgs.gnugrep pkgs.socat ]; 12 runtimeInputs = [ niri pkgs.gojq pkgs.gnugrep pkgs.socat ];
14 text = '' 13 text = ''
15 app_id="$1" 14 window_select="$1"
16 shift 15 shift
17 workspace_name="$1" 16 workspace_name="$1"
18 shift 17 shift
19 18
20 workspaces_json="$(niri msg -j workspaces)" 19 workspaces_json="$(niri msg -j workspaces)"
21 workspace_output="$(jq -r --arg workspace_name "$workspace_name" '.[] | select(.name == $workspace_name) | .output' <<<"$workspaces_json")" 20 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")" 21 # 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" 22 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
24 socat STDIO "$NIRI_SOCKET" <<<'{"Action":{"FocusWorkspace":{"reference":{"Id":'"''${active_workspace}"'}}}}' 23 if [[ $workspace_output != "$active_output" ]]; then
25 niri msg action move-workspace-to-index --index 1 "$workspace_name" 24 niri msg action move-workspace-to-monitor --reference "$workspace_name" "$active_output"
25 # socat STDIO "$NIRI_SOCKET" <<<'{"Action":{"FocusWorkspace":{"reference":{"Id":'"''${active_workspace}"'}}}}'
26 # niri msg action move-workspace-to-index --reference "$workspace_name" 1
27 fi
26 28
27 while IFS=$'\n' read -r window_json; do 29 while IFS=$'\n' read -r window_json; do
28 if jq -r '.app_id' <<<"$window_json" | grep -q "$app_id"; then 30 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then
29 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")" 31 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then
32 niri msg action focus-workspace-previous
33 else
34 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
35 fi
30 exit 0 36 exit 0
31 fi 37 fi
32 done < <(niri msg -j windows | jq -c '.[]') 38 done < <(niri msg -j windows | jq -c '.[]')
@@ -34,10 +40,93 @@ let
34 exec "$@" 40 exec "$@"
35 ''; 41 '';
36 }; 42 };
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); 43 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
46 with_adjacent_workspace = pkgs.writeShellApplication {
47 name = "with-adjacent-workspace";
48 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
49 text = ''
50 blacklist="$1"
51 shift
52 direction="$1"
53 shift
54 action="$1"
55 shift
56
57 workspaces_json="$(niri msg -j workspaces)"
58 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
59 workspace_output="$(jq -r --arg active_workspace "$active_workspace" '.[] | select(.id == ($active_workspace | tonumber)) | .output' <<<"$workspaces_json")"
60 workspace_idx="$(jq -r '.[] | select(.is_focused) | .idx' <<<"$workspaces_json")"
61
62 jq_script='map(select('
63 case "$direction" in
64 down)
65 # shellcheck disable=SC2016
66 jq_script=''${jq_script}'.idx > ($workspace_idx | tonumber)';;
67 up)
68 # shellcheck disable=SC2016
69 jq_script=''${jq_script}'.idx < ($workspace_idx | tonumber)';;
70 esac
71 # shellcheck disable=SC2016
72 jq_script=''${jq_script}' and .output == $workspace_output and ((.name == null) or (.name | test($blacklist) | not)))) | sort_by(.idx)'
73 [[ $direction == "up" ]] && jq_script=''${jq_script}' | reverse'
74 jq_script=''${jq_script}' | .[0]'
75
76 workspace_json=$(jq -c --arg blacklist "$blacklist" --arg workspace_output "$workspace_output" --arg workspace_idx "$workspace_idx" "$jq_script" <<<"$workspaces_json")
77 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
78 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
79 '';
80 };
81 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^pwctl|eff|kpxc|bmgr|edit|term$";
82 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}}}}'';
84
85 with_unnamed_workspace = pkgs.writeShellApplication {
86 name = "with-unnamed-workspace";
87 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
88 text = ''
89 action="$1"
90 shift
91
92 workspaces_json="$(niri msg -j workspaces)"
93 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
94 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
95
96 history_json="$(socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/niri-workspace-history.sock)"
97 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")"
98 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
99 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
100 '';
101 };
102 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
103
104 with_select_window = pkgs.writeShellApplication {
105 name = "with-select-window";
106 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
107 text = ''
108 window_select="$1"
109 shift
110 action="$1"
111 shift
112
113 windows_json="$(niri msg -j windows)"
114 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)"
116 # 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")"
118
119 [[ -z "$window_json" ]] && exit 1
120
121 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
122 '';
123 };
124 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
38in { 125in {
39 imports = [ 126 imports = [
40 ./waybar.nix 127 ./waybar.nix
128 ./mako.nix
129 ./swayosd.nix
41 ]; 130 ];
42 131
43 config = { 132 config = {
@@ -72,6 +161,113 @@ in {
72 ]; 161 ];
73 }; 162 };
74 163
164 systemd.user.sockets.niri-workspace-history = {
165 Socket = {
166 ListenStream = "%t/niri-workspace-history.sock";
167 SocketMode = "0600";
168 };
169 };
170 systemd.user.services.niri-workspace-history = {
171 Unit = {
172 BindsTo = [ "niri.service" ];
173 After = [ "niri.service" ];
174 };
175 Install = {
176 WantedBy = [ "niri.service" ];
177 };
178 Service = {
179 Type = "simple";
180 Sockets = [ "niri-workspace-history.socket" ];
181 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" {} ''
182 import os
183 import socket
184 import json
185 import sys
186 from collections import defaultdict
187 from threading import Thread, Lock
188 from socketserver import StreamRequestHandler, ThreadingTCPServer
189 from contextlib import contextmanager
190 from io import TextIOWrapper
191
192
193 @contextmanager
194 def detaching(thing):
195 try:
196 yield thing
197 finally:
198 thing.detach()
199
200
201 workspace_history = defaultdict(list)
202 history_lock = Lock()
203
204
205 def monitor_niri():
206 workspaces = list()
207
208 def focus_workspace(output, workspace):
209 global workspace_history, history_lock
210
211 with history_lock:
212 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace] # noqa: E501
213 print(json.dumps(workspace_history), file=sys.stderr)
214
215 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
216 sock.connect(os.environ["NIRI_SOCKET"])
217 sock.send(b"\"EventStream\"\n")
218 for line in sock.makefile(buffering=1, encoding='utf-8'):
219 if line_json := json.loads(line):
220 if "WorkspacesChanged" in line_json:
221 workspaces = line_json["WorkspacesChanged"]["workspaces"]
222 for ws in workspaces:
223 if ws["is_focused"]:
224 focus_workspace(ws["output"], ws["id"])
225 if "WorkspaceActivated" in line_json:
226 for ws in workspaces:
227 if ws["id"] != line_json["WorkspaceActivated"]["id"]:
228 continue
229 focus_workspace(ws["output"], ws["id"])
230 break
231
232
233 class RequestHandler(StreamRequestHandler):
234 def handle(self):
235 global workspace_history, history_lock
236
237 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out: # noqa: E501
238 with history_lock:
239 json.dump(workspace_history, out)
240
241
242 class Server(ThreadingTCPServer):
243 def __init__(self):
244 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False) # noqa: E501
245 self.socket = socket.fromfd(3, self.address_family, self.socket_type)
246
247
248 def run_server():
249 with Server() as server:
250 server.serve_forever()
251
252
253 niri = Thread(target=monitor_niri)
254 niri.daemon = True
255 niri.start()
256
257 server_thread = Thread(target=run_server)
258 server_thread.daemon = True
259 server_thread.start()
260
261 while True:
262 server_thread.join(timeout=0.5)
263 niri.join(timeout=0.5)
264
265 if not (niri.is_alive() and server_thread.is_alive()):
266 break
267 '';
268 };
269 };
270
75 programs.niri.settings = { 271 programs.niri.settings = {
76 prefer-no-csd = true; 272 prefer-no-csd = true;
77 screenshot-path = "${config.home.homeDirectory}/screenshots"; 273 screenshot-path = "${config.home.homeDirectory}/screenshots";
@@ -79,10 +275,15 @@ in {
79 hotkey-overlay.skip-at-startup = true; 275 hotkey-overlay.skip-at-startup = true;
80 276
81 input = { 277 input = {
82 keyboard.xkb = { 278 keyboard = {
83 layout = "us,us"; 279 repeat-delay = 300;
84 variant = "dvp,"; 280 repeat-rate = 50;
85 options = "compose:caps,grp:win_space_toggle"; 281
282 xkb = {
283 layout = "us,us";
284 variant = "dvp,";
285 options = "compose:caps,grp:win_space_toggle";
286 };
86 }; 287 };
87 288
88 workspace-auto-back-and-forth = true; 289 workspace-auto-back-and-forth = true;
@@ -91,7 +292,7 @@ in {
91 }; 292 };
92 293
93 outputs = { 294 outputs = {
94 "Samsung Display Corp. 0x4141 Unknown" = { 295 "eDP-1" = {
95 scale = 1.5; 296 scale = 1.5;
96 position = { x = 0; y = 0; }; 297 position = { x = 0; y = 0; };
97 }; 298 };
@@ -99,38 +300,95 @@ in {
99 scale = 1.5; 300 scale = 1.5;
100 position = { x = 2560; y = 0; }; 301 position = { x = 2560; y = 0; };
101 }; 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 };
102 }; 309 };
103 310
104 environment = { 311 environment = {
105 NIXOS_OZONE_WL = "1"; 312 NIXOS_OZONE_WL = "1";
106 QT_QPA_PLATFORM = "wayland"; 313 QT_QPA_PLATFORM = "wayland";
314 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
107 GDK_BACKEND = "wayland"; 315 GDK_BACKEND = "wayland";
108 SDL_VIDEODRIVER = "wayland"; 316 SDL_VIDEODRIVER = "wayland";
317 DISPLAY = ":0";
318 };
319
320 debug.render-drm-device = "/dev/dri/by-path/pci-0000:00:02.0-render";
321
322 animations = {
323 slowdown = 0.5;
324 workspace-switch = null;
109 }; 325 };
110 326
111 layout = { 327 layout = {
112 gaps = 8; 328 gaps = 8;
113 struts = { left = 8; right = 8; top = 0; bottom = 0; }; 329 struts = { left = 0; right = 0; top = 0; bottom = 0; };
114 focus-ring = { 330 focus-ring = {
115 width = 2; 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 };
116 }; 344 };
345
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
361 always-center-single-column = true;
117 }; 362 };
118 363
119 cursor.hide-when-typing = true; 364 cursor.hide-when-typing = true;
120 365
366 input = {
367 touchpad.enable = false;
368 trackball = {
369 scroll-method = "on-button-down";
370 scroll-button = 278;
371 };
372 };
373
121 workspaces = { 374 workspaces = {
122 "001".name = "pwctl"; 375 "001" = { name = "pwctl"; open-on-output = "eDP-1"; };
123 "002".name = "kpxc"; 376 "002" = { name = "kpxc"; open-on-output = "eDP-1"; };
124 "003".name = "bmgr"; 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"; };
125 "101".name = "comm"; 381 "101".name = "comm";
126 "102".name = "web"; 382 "102".name = "web";
127 "104".name = "read"; 383 # "104".name = "read";
128 "105".name = "mon"; 384 # "105".name = "mon";
129 "110".name = "vid"; 385 "110".name = "vid";
386 "120".name = "bmr";
130 }; 387 };
131 388
132 window-rules = [ 389 window-rules = [
133 { 390 {
391 matches = [ { is-floating = true; } ];
134 geometry-corner-radius = 392 geometry-corner-radius =
135 let 393 let
136 allCorners = r: { bottom-left = r; bottom-right = r; top-left = r; top-right = r; }; 394 allCorners = r: { bottom-left = r; bottom-right = r; top-left = r; top-right = r; };
@@ -140,50 +398,130 @@ in {
140 { 398 {
141 matches = [ { app-id = "^com\.saivert\.pwvucontrol$"; } ]; 399 matches = [ { app-id = "^com\.saivert\.pwvucontrol$"; } ];
142 open-on-workspace = "pwctl"; 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;
143 } 407 }
144 { 408 {
145 matches = [ { app-id = "^\.blueman-manager-wrapped$"; } ]; 409 matches = [ { app-id = "^\.blueman-manager-wrapped$"; } ];
146 open-on-workspace = "bmgr"; 410 open-on-workspace = "bmgr";
411 open-maximized = true;
412 }
413 {
414 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
415 block-out-from = "screencast";
147 } 416 }
148 { 417 {
149 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ]; 418 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
150 excludes = [ 419 excludes = [
151 { title = "^Unlock Database"; } 420 { title = "^Unlock Database.*"; }
152 { title = "^Access Request"; } 421 { title = "^Access Request.*"; }
153 { title = "^Passkey credentials"; } 422 { title = ".*Passkey credentials$"; }
154 ]; 423 ];
155 open-on-workspace = "kpxc"; 424 open-on-workspace = "kpxc";
425 open-maximized = true;
156 open-focused = false; 426 open-focused = false;
157 } 427 }
158 { 428 {
159 matches = [ 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 = [
160 { app-id = "^thunderbird$"; } 463 { app-id = "^thunderbird$"; }
161 { app-id = "^Element$"; } 464 { app-id = "^Element$"; }
465 { app-id = "^Rainbow$"; }
162 ]; 466 ];
163 open-on-workspace = "comm"; 467 open-on-workspace = "comm";
164 } 468 }
165 { 469 {
166 matches = [ { app-id = "^firefox$"; } ]; 470 matches = [ { app-id = "^firefox$"; } ];
167 open-on-workspace = "web"; 471 open-on-workspace = "web";
472 open-maximized = true;
473 variable-refresh-rate = true;
168 } 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 # }
169 { 483 {
170 matches = [ 484 matches = [ { app-id = "^mpv$"; } ];
171 { app-id = "^evince$"; } 485 open-on-workspace = "vid";
172 { app-id = "^imv$"; } 486 default-column-width.proportion = 1.;
173 { app-id = "^org\.pwmt\.zathura$"; } 487 variable-refresh-rate = true;
174 ];
175 open-on-workspace = "read";
176 } 488 }
177 { 489 {
178 matches = [ { app-id = "^mpv$"; } ]; 490 matches = [ { app-id = "^kitty-play$"; } ];
179 open-on-workspace = "vid"; 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;
180 } 509 }
181 { 510 {
182 matches = [ 511 matches = [
183 { app-id = "^qemu$"; } 512 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
184 { app-id = "^virt-manager$"; } 513 { app-id = "^org\.kde\.polkit-kde-authentication-agent-1$"; }
514 { app-id = "^xdg-desktop-portal-gtk$"; }
185 ]; 515 ];
186 open-on-workspace = "mon"; 516 open-floating = true;
517 }
518 ];
519 layer-rules = [
520 { matches = [
521 { namespace = "^notifications$"; }
522 { namespace = "^waybar$"; }
523 ];
524 block-out-from = "screencast";
187 } 525 }
188 ]; 526 ];
189 527
@@ -192,8 +530,93 @@ in {
192 530
193 "Mod+Return".action = spawn terminal; 531 "Mod+Return".action = spawn terminal;
194 "Mod+Q".action = close-window; 532 "Mod+Q".action = close-window;
195 "Mod+D".action = spawn (lib.getExe config.programs.fuzzel.package); 533 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
196 "Mod+Shift+D".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path"; 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}}}";
197 620
198 "Mod+H".action = focus-column-left; 621 "Mod+H".action = focus-column-left;
199 "Mod+T".action = focus-window-down; 622 "Mod+T".action = focus-window-down;
@@ -215,15 +638,33 @@ in {
215 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up; 638 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
216 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right; 639 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
217 640
218 "Mod+G".action = focus-workspace-down; 641 "Mod+G".action = focus-adjacent-workspace "down";
219 "Mod+C".action = focus-workspace-up; 642 "Mod+C".action = focus-adjacent-workspace "up";
220 643
221 "Mod+Shift+G".action = move-column-to-workspace-down; 644 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
222 "Mod+Shift+C".action = move-column-to-workspace-up; 645 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
223 646
224 "Mod+Shift+Control+G".action = move-workspace-down; 647 "Mod+Shift+Control+G".action = move-workspace-down;
225 "Mod+Shift+Control+C".action = move-workspace-up; 648 "Mod+Shift+Control+C".action = move-workspace-up;
226 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
227 "Mod+M".action = consume-or-expel-window-left; 668 "Mod+M".action = consume-or-expel-window-left;
228 "Mod+W".action = consume-or-expel-window-right; 669 "Mod+W".action = consume-or-expel-window-right;
229 670
@@ -233,8 +674,8 @@ in {
233 "Mod+Shift+F".action = maximize-column; 674 "Mod+Shift+F".action = maximize-column;
234 "Mod+Shift+Ctrl+F".action = fullscreen-window; 675 "Mod+Shift+Ctrl+F".action = fullscreen-window;
235 676
236 "Mod+B".action = switch-focus-between-floating-and-tiling; 677 "Mod+V".action = switch-focus-between-floating-and-tiling;
237 "Mod+Shift+B".action = toggle-window-floating; 678 "Mod+Shift+V".action = toggle-window-floating;
238 679
239 "Mod+Left".action = set-column-width "-10%"; 680 "Mod+Left".action = set-column-width "-10%";
240 "Mod+Down".action = set-window-height "-10%"; 681 "Mod+Down".action = set-window-height "-10%";
@@ -245,44 +686,57 @@ in {
245 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors"; 686 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
246 allow-when-locked = true; 687 allow-when-locked = true;
247 }; 688 };
248 "Mod+Shift+L" = { 689 "Mod+Shift+L".action = spawn loginctl "lock-session";
249 action = spawn loginctl "lock-session";
250 };
251 "Mod+Shift+E".action = quit; 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 };
252 703
253 "XF86MonBrightnessUp" = { 704 "XF86MonBrightnessUp" = {
254 action = spawn lightctl "-d" "-e4" "-n1" "up"; 705 action = spawn swayosd-client "--brightness" "raise";
255 allow-when-locked = true; 706 allow-when-locked = true;
256 }; 707 };
257 "XF86MonBrightnessDown" = { 708 "XF86MonBrightnessDown" = {
258 action = spawn lightctl "-d" "-e4" "-n1" "down"; 709 action = spawn swayosd-client "--brightness" "lower";
259 allow-when-locked = true; 710 allow-when-locked = true;
260 }; 711 };
261 "XF86AudioRaiseVolume" = { 712 "XF86AudioRaiseVolume" = {
262 action = spawn volumectl "-d" "-u" "up"; 713 action = spawn swayosd-client "--output-volume" "raise";
263 allow-when-locked = true; 714 allow-when-locked = true;
264 }; 715 };
265 "XF86AudioLowerVolume" = { 716 "XF86AudioLowerVolume" = {
266 action = spawn volumectl "-d" "-u" "down"; 717 action = spawn swayosd-client "--output-volume" "lower";
267 allow-when-locked = true; 718 allow-when-locked = true;
268 }; 719 };
269 "XF86AudioMute" = { 720 "XF86AudioMute" = {
270 action = spawn volumectl "-d" "toggle-mute"; 721 action = spawn swayosd-client "--output-volume" "mute-toggle";
271 allow-when-locked = true; 722 allow-when-locked = true;
272 }; 723 };
273 "XF86AudioMicMute" = { 724 "XF86AudioMicMute" = {
274 action = spawn volumectl "-d" "-m" "toggle-mute"; 725 action = spawn swayosd-client "--input-volume" "mute-toggle";
275 allow-when-locked = true; 726 allow-when-locked = true;
276 }; 727 };
277 728
278 "Mod+Semicolon".action = spawn dunstctl "close"; 729 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
279 "Mod+Shift+Semicolon".action = spawn dunstctl "close-all"; 730 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
280 "Mod+Period".action = spawn dunstctl "context"; 731 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
281 "Mod+Comma".action = spawn dunstctl "history-pop"; 732 "Mod+Comma".action = spawn makoctl "restore";
282 733
283 "Mod+Alt+A".action = focus-or-spawn-action "^com\.saivert\.pwvucontrol$" "pwctl" "pwvucontrol"; 734 "Mod+Control+A".action = focus-or-spawn-action-app_id "com.saivert.pwvucontrol" "pwctl" "pwvucontrol";
284 "Mod+Alt+P".action = focus-or-spawn-action "^org\.keepassxc\.KeePassXC$" "kpxc" "keepassxc"; 735 "Mod+Control+O".action = focus-or-spawn-action-app_id "com.github.wwmm.easyeffects" "eff" "easyeffects";
285 "Mod+Alt+B".action = focus-or-spawn-action "^\.blueman-manager-wrapped$" "bmgr" "blueman-manager"; 736 "Mod+Control+P".action = focus-or-spawn-action-app_id "org.keepassxc.KeePassXC" "kpxc" "keepassxc";
737 "Mod+Control+B".action = focus-or-spawn-action-app_id ".blueman-manager-wrapped" "bmgr" "blueman-manager";
738 "Mod+Control+Return".action = focus-or-spawn-action-app_id "kitty-scratch" "term" "kitty" "--app-id" "kitty-scratch";
739 "Mod+Control+E".action = focus-or-spawn-action "select(.app_id == \"emacs\" and .title == \"scratch\")" "edit" "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))";
286 }; 740 };
287 }; 741 };
288 }; 742 };
diff --git a/accounts/gkleen@sif/niri/mako.nix b/accounts/gkleen@sif/niri/mako.nix
new file mode 100644
index 00000000..0a10555a
--- /dev/null
+++ b/accounts/gkleen@sif/niri/mako.nix
@@ -0,0 +1,115 @@
1{ config, lib, pkgs, ... }:
2{
3 config = {
4 services.mako = {
5 enable = true;
6 font = "Fira Sans 10";
7 format = "<i>%s</i>\\n%b";
8 margin = "2";
9 maxVisible = -1;
10 backgroundColor = "#000000dd";
11 progressColor = "source #223544ff";
12 width = 384;
13 extraConfig = ''
14 outer-margin=1
15 max-history=100
16 max-icon-size=48
17
18 [grouped]
19 format=<b>(%g)</b> <i>%s</i>\n%b
20
21 [urgency=low]
22 text-color=#999999ff
23
24 [urgency=critical]
25 background-color=#900000dd
26
27 [app-name=Element]
28 group-by=summary
29
30 [mode=silent]
31 invisible=1
32 '';
33 package = pkgs.symlinkJoin {
34 name = "${pkgs.mako.name}-wrapped";
35 paths = with pkgs; [ mako ];
36 inherit (pkgs.mako) meta;
37 postBuild = ''
38 rm -r $out/share/dbus-1
39 '';
40 };
41 };
42 systemd.user.services.mako = {
43 Unit = {
44 Description = "Mako notification daemon";
45 PartOf = [ "graphical-session.target" ];
46 };
47 Install = {
48 WantedBy = [ "graphical-session.target" ];
49 };
50 Service = {
51 Type = "dbus";
52 BusName = "org.freedesktop.Notifications";
53 ExecStart = lib.getExe config.services.mako.package;
54 RestartSec = 5;
55 Restart = "always";
56 };
57 };
58
59 systemd.user.services.mako-follows-focus = {
60 Unit = {
61 BindsTo = [ "niri.service" "mako.service" ];
62 After = [ "niri.service" "mako.service" ];
63 };
64 Service = {
65 Type = "simple";
66 Restart = "always";
67 ExecStart = pkgs.writers.writePython3 "mako-follows-focus" {
68 libraries = with pkgs.python3Packages; [];
69 } ''
70 import os
71 import socket
72 import json
73 import subprocess
74
75
76 current_output = None
77 workspaces = []
78
79
80 def output_changed(new_output):
81 global current_output
82
83 if current_output == new_output:
84 return
85
86 current_output = new_output
87 subprocess.run(["makoctl", "reload"])
88
89
90 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
91 sock.connect(os.environ["NIRI_SOCKET"])
92 sock.send(b"\"EventStream\"\n")
93 for line in sock.makefile(buffering=1, encoding='utf-8'):
94 if line_json := json.loads(line):
95 if "WorkspacesChanged" in line_json:
96 workspaces = line_json["WorkspacesChanged"]["workspaces"]
97 for workspace in workspaces:
98 if not workspace["is_focused"]:
99 continue
100 output_changed(workspace["output"])
101 break
102 if "WorkspaceActivated" in line_json and line_json["WorkspaceActivated"]["focused"]: # noqa: E501
103 for workspace in workspaces:
104 if not workspace["id"] == line_json["WorkspaceActivated"]["id"]: # noqa: E501
105 continue
106 output_changed(workspace["output"])
107 break
108 '';
109 };
110 Install = {
111 WantedBy = [ "mako.service" ];
112 };
113 };
114 };
115}
diff --git a/accounts/gkleen@sif/niri/swayosd.nix b/accounts/gkleen@sif/niri/swayosd.nix
new file mode 100644
index 00000000..984927c2
--- /dev/null
+++ b/accounts/gkleen@sif/niri/swayosd.nix
@@ -0,0 +1,65 @@
1{ pkgs, ... }:
2{
3 config = {
4 services.swayosd = {
5 enable = true;
6 topMargin = 0.946154;
7 stylePath = pkgs.runCommand "style.css" {
8 src = pkgs.writeText "style.scss" ''
9 window#osd {
10 padding: 12px 20px;
11 border-radius: 999px;
12 border: none;
13 background: rgba(0, 0, 0, 0.87);
14
15 #container {
16 margin: 16px;
17 }
18
19 image,
20 label {
21 color: rgb(255, 255, 255);
22
23 &:disabled {
24 opacity: 1;
25 color: rgb(84, 84, 84);
26 }
27 }
28
29 progressbar {
30 min-height: 6px;
31 border-radius: 999px;
32 background: transparent;
33 border: none;
34
35 trough, progress {
36 min-height: inherit;
37 border-radius: inherit;
38 border: none;
39 }
40
41 trough {
42 background: rgb(127, 127, 127);
43 }
44 progress {
45 background: rgb(255, 255, 255);
46 }
47
48 &:disabled {
49 opacity: 1;
50
51 trough {
52 background: rgb(19, 19, 19);
53 }
54 progress {
55 background: rgb(38, 38, 38);
56 }
57 }
58 }
59 }
60 '';
61 buildInputs = with pkgs; [sass];
62 } "scss -C --sourcemap=none --style=compact $src $out";
63 };
64 };
65}
diff --git a/accounts/gkleen@sif/niri/waybar.nix b/accounts/gkleen@sif/niri/waybar.nix
index 2d00c6d8..3f1f8119 100644
--- a/accounts/gkleen@sif/niri/waybar.nix
+++ b/accounts/gkleen@sif/niri/waybar.nix
@@ -1,5 +1,7 @@
1{ lib, pkgs, ... }: 1{ lib, config, pkgs, ... }:
2{ 2let
3 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
4in {
3 config = { 5 config = {
4 programs.waybar = { 6 programs.waybar = {
5 enable = true; 7 enable = true;
@@ -22,16 +24,66 @@
22 output = [ "eDP-1" "DP-2" "DP-3" ]; 24 output = [ "eDP-1" "DP-2" "DP-3" ];
23 modules-left = [ "niri/workspaces" ]; 25 modules-left = [ "niri/workspaces" ];
24 modules-center = [ "niri/window" ]; 26 modules-center = [ "niri/window" ];
25 modules-right = [ # "custom/worktime" "custom/worktime-today" 27 modules-right = [ "custom/worktime" "custom/worktime-today"
26 "custom/weather" 28 "custom/weather"
27 # "custom/keymap" 29 "custom/keymap"
28 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "clock" ]; 30 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "clock" ];
29 31
32 "custom/mako" = {
33 format = "{}";
34 return-type = "json";
35 exec = pkgs.writers.writePython3 "mako-silent" { libraries = [ pkgs.python3Packages.dbus-next ]; } ''
36 from dbus_next.aio import MessageBus
37
38 import asyncio
39
40 import json
41
42
43 loop = asyncio.new_event_loop()
44 asyncio.set_event_loop(loop)
45
46
47 async def main():
48 bus = await MessageBus().connect()
49 # the introspection xml would normally be included in your project, but
50 # this is convenient for development
51 introspection = await bus.introspect('org.freedesktop.Notifications', '/fr/emersion/Mako') # noqa: E501
52
53 obj = bus.get_proxy_object('org.freedesktop.Notifications', '/fr/emersion/Mako', introspection) # noqa: E501
54 mako = obj.get_interface('fr.emersion.Mako')
55 properties = obj.get_interface('org.freedesktop.DBus.Properties')
56
57 async def print_mode():
58 modes = await mako.get_modes()
59 is_silent = "silent" in modes
60 icon = "&#xf009b;" if is_silent else "&#xf009a;"
61 text = f"<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>" # noqa: E501
62 if is_silent:
63 text = f"<span color=\"#ffffff\">{text}</span>"
64 print(json.dumps({'text': text, 'tooltip': ', '.join(modes)}, separators=(',', ':')), flush=True) # noqa: E501
65
66 async def on_properties_changed(interface_name, changed_properties, invalidated_properties): # noqa: E501
67 if "Modes" not in invalidated_properties:
68 return
69
70 await print_mode()
71
72 properties.on_properties_changed(on_properties_changed)
73 await print_mode()
74
75 await loop.create_future()
76
77
78 loop.run_until_complete(main())
79 '';
80 on-click = "makoctl mode -t silent";
81 };
30 "custom/weather" = { 82 "custom/weather" = {
31 format = "{}"; 83 format = "{}";
32 tooltip = true; 84 tooltip = true;
33 interval = 3600; 85 interval = 3600;
34 exec = "${lib.getExe pkgs.wttrbar} --hide-conditions --nerd --custom-indicator \"<span font=\\\"Symbols Nerd Font Mono\\\" size=\\\"120%\\\">{ICON}</span> {FeelsLikeC}°\""; 86 exec = "${lib.getExe pkgs.wttrbar} --hide-conditions --nerd --custom-indicator \"<span font=\\\"Symbols Nerd Font Mono\\\" size=\\\"100%\\\">{ICON}</span> {FeelsLikeC}°\"";
35 return-type = "json"; 87 return-type = "json";
36 }; 88 };
37 "custom/keymap" = { 89 "custom/keymap" = {
@@ -41,8 +93,6 @@
41 exec = pkgs.writers.writePython3 "keymap" {} '' 93 exec = pkgs.writers.writePython3 "keymap" {} ''
42 import os 94 import os
43 import socket 95 import socket
44 import re
45 import subprocess
46 import json 96 import json
47 97
48 98
@@ -55,32 +105,34 @@
55 print(json.dumps({'text': short, 'tooltip': keymap}, separators=(',', ':')), flush=True) # noqa: E501 105 print(json.dumps({'text': short, 'tooltip': keymap}, separators=(',', ':')), flush=True) # noqa: E501
56 106
57 107
58 r = subprocess.run(["hyprctl", "devices", "-j"], check=True, stdout=subprocess.PIPE, text=True) # noqa: E501 108 keyboard_layouts = []
59 for keyboard in json.loads(r.stdout)['keyboards']:
60 if keyboard['name'] != "at-translated-set-2-keyboard":
61 continue
62 output(keyboard['active_keymap'])
63 109
64 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 110 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
65 sock.connect(os.environ["XDG_RUNTIME_DIR"] + "/hypr/" + os.environ["HYPRLAND_INSTANCE_SIGNATURE"] + "/.socket2.sock") # noqa: E501 111 sock.connect(os.environ["NIRI_SOCKET"])
66 expected = re.compile(r'^activelayout>>at-translated-set-2-keyboard,(?P<keymap>.+)$') # noqa: E501 112 sock.send(b"\"EventStream\"\n")
67 for line in sock.makefile(buffering=1, encoding='utf-8'): 113 for line in sock.makefile(buffering=1, encoding='utf-8'):
68 if match := expected.match(line): 114 if line_json := json.loads(line):
69 output(match.group("keymap")) 115 if "KeyboardLayoutsChanged" in line_json:
116 keyboard_layouts = line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["names"] # noqa: E501
117 output(keyboard_layouts[line_json["KeyboardLayoutsChanged"]["keyboard_layouts"]["current_idx"]]) # noqa: E501
118 if "KeyboardLayoutSwitched" in line_json:
119 output(keyboard_layouts[line_json["KeyboardLayoutSwitched"]["idx"]]) # noqa: E501
70 ''; 120 '';
71 on-click = "hyprctl switchxkblayout at-translated-set-2-keyboard next"; 121 on-click = "niri msg action switch-layout next";
72 }; 122 };
73 "custom/worktime" = { 123 "custom/worktime" = {
74 interval = 60; 124 interval = 60;
75 exec = lib.getExe pkgs.worktime; 125 exec = "${lib.getExe pkgs.worktime} time --waybar";
76 tooltip = false; 126 return-type = "json";
77 }; 127 };
78 "custom/worktime-today" = { 128 "custom/worktime-today" = {
79 interval = 60; 129 interval = 60;
80 exec = "${lib.getExe pkgs.worktime} today"; 130 exec = "${lib.getExe pkgs.worktime} today --waybar";
81 tooltip = false; 131 return-type = "json";
132 };
133 "niri/workspaces" = {
134 ignore = ["eff" "pwctl" "kpxc" "bmgr" "edit" "term"];
82 }; 135 };
83 "niri/workspaces" = {};
84 "niri/window" = { 136 "niri/window" = {
85 separate-outputs = true; 137 separate-outputs = true;
86 icon = true; 138 icon = true;
@@ -140,8 +192,8 @@
140 icon-size = iconSize; 192 icon-size = iconSize;
141 tooltip-format = "{percent}%"; 193 tooltip-format = "{percent}%";
142 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"]; 194 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"];
143 on-scroll-up = "lightctl -d -e4 -n1 up"; 195 on-scroll-up = "${swayosd-client} --brightness raise";
144 on-scroll-down = "lightctl -d -e4 -n1 down"; 196 on-scroll-down = "${swayosd-client} --brightness lower";
145 }; 197 };
146 wireplumber = { 198 wireplumber = {
147 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>"; 199 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
@@ -150,9 +202,9 @@
150 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"]; 202 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"];
151 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>"; 203 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>";
152 # ignored-sinks = ["Easy Effects Sink"]; 204 # ignored-sinks = ["Easy Effects Sink"];
153 on-scroll-up = "volumectl -d -u up"; 205 on-scroll-up = "${swayosd-client} --output-volume raise";
154 on-scroll-down = "volumectl -d -u down"; 206 on-scroll-down = "${swayosd-client} --output-volume lower";
155 on-click = "volumectl -d toggle-mute"; 207 on-click = "${swayosd-client} --output-volume mute-toggle";
156 }; 208 };
157 } 209 }
158 { 210 {
@@ -164,7 +216,9 @@
164 modules-center = [ "niri/window" ]; 216 modules-center = [ "niri/window" ];
165 modules-right = [ "clock" ]; 217 modules-right = [ "clock" ];
166 218
167 "niri/workspaces" = {}; 219 "niri/workspaces" = {
220 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"];
221 };
168 "niri/window" = { 222 "niri/window" = {
169 separate-outputs = true; 223 separate-outputs = true;
170 icon = true; 224 icon = true;
@@ -189,7 +243,7 @@
189 243
190 * { 244 * {
191 border: none; 245 border: none;
192 font-family: "Fira Sans Nerd Font"; 246 font-family: "Fira Sans";
193 font-size: 10pt; 247 font-size: 10pt;
194 min-height: 0; 248 min-height: 0;
195 } 249 }
@@ -200,10 +254,10 @@
200 } 254 }
201 255
202 .modules-left { 256 .modules-left {
203 margin-left: 9px; 257 margin-left: 12px;
204 } 258 }
205 .modules-right { 259 .modules-right {
206 margin-right: 9px; 260 margin-right: 12px;
207 } 261 }
208 262
209 .module { 263 .module {
@@ -228,21 +282,26 @@
228 color: @grey; 282 color: @grey;
229 margin: 0 5px; 283 margin: 0 5px;
230 } 284 }
231 #custom-weather, #custom-worktime-today { 285 #custom-weather {
232 margin-right: 3px; 286 margin-right: 3px;
233 } 287 }
234 #custom-keymap, #custom-weather { 288 #custom-keymap {
235 margin-left: 3px; 289 margin-left: 3px;
290 margin-right: 3px;
236 } 291 }
237 292
238 #tray { 293 #tray {
239 margin: 0; 294 margin: 0;
240 } 295 }
241 #battery, #idle_inhibitor, #backlight, #wireplumber { 296 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako {
242 color: @grey; 297 color: @grey;
243 margin: 0 5px 0 2px; 298 margin: 0 5px 0 2px;
244 } 299 }
245 #idle_inhibitor { 300 #idle_inhibitor {
301 margin-right: 4px;
302 margin-left: 6px;
303 }
304 #custom-mako {
246 margin-right: 2px; 305 margin-right: 2px;
247 margin-left: 3px; 306 margin-left: 3px;
248 } 307 }
@@ -264,6 +323,12 @@
264 #idle_inhibitor.activated { 323 #idle_inhibitor.activated {
265 color: @white; 324 color: @white;
266 } 325 }
326 #custom-worktime.running, #custom-worktime-today.running {
327 color: @white;
328 }
329 #custom-worktime.over, #custom-worktime-today.over {
330 color: @orange;
331 }
267 332
268 #idle_inhibitor { 333 #idle_inhibitor {
269 padding-top: 1px; 334 padding-top: 1px;
@@ -271,7 +336,7 @@
271 336
272 #privacy { 337 #privacy {
273 color: @red; 338 color: @red;
274 margin: -1px 2px 0px 5px; 339 margin: -1px 4px 0px 3px;
275 } 340 }
276 #clock { 341 #clock {
277 /* margin-right: 5px; */ 342 /* margin-right: 5px; */