summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--_sources/generated.json8
-rw-r--r--_sources/generated.nix8
-rw-r--r--accounts/gkleen@sif/niri/default.nix1119
-rw-r--r--accounts/gkleen@sif/niri/waybar.nix4
-rw-r--r--flake.lock58
-rw-r--r--flake.nix2
-rw-r--r--hosts/sif/default.nix8
-rw-r--r--hosts/sif/greetd/default.nix5
-rw-r--r--overlays/swayosd/default.nix (renamed from overlays/swayosd.nix)3
-rw-r--r--overlays/swayosd/exponential.patch57
-rwxr-xr-xoverlays/worktime/worktime/__main__.py30
11 files changed, 780 insertions, 522 deletions
diff --git a/_sources/generated.json b/_sources/generated.json
index 72f913ec..b3d09fc4 100644
--- a/_sources/generated.json
+++ b/_sources/generated.json
@@ -407,7 +407,7 @@
407 }, 407 },
408 "v4l2loopback": { 408 "v4l2loopback": {
409 "cargoLocks": null, 409 "cargoLocks": null,
410 "date": "2025-01-18", 410 "date": "2025-02-03",
411 "extract": null, 411 "extract": null,
412 "name": "v4l2loopback", 412 "name": "v4l2loopback",
413 "passthru": null, 413 "passthru": null,
@@ -419,12 +419,12 @@
419 "name": null, 419 "name": null,
420 "owner": "umlaeute", 420 "owner": "umlaeute",
421 "repo": "v4l2loopback", 421 "repo": "v4l2loopback",
422 "rev": "39ad8a43522c18b5e4f4363ce053f604312fc413", 422 "rev": "7164d6e6b9aad52a27652c8bb8bd3c3d7a5b336b",
423 "sha256": "sha256-A1p5ZfoMlw6/J3vBdQcXMvERdyBnqs9Ca+0LcLnu7b8=", 423 "sha256": "sha256-1f4+pIbPM/TOJOc7Ns2VDXlBCGyrXiNpmKfThl5kZfk=",
424 "sparseCheckout": [], 424 "sparseCheckout": [],
425 "type": "github" 425 "type": "github"
426 }, 426 },
427 "version": "39ad8a43522c18b5e4f4363ce053f604312fc413" 427 "version": "7164d6e6b9aad52a27652c8bb8bd3c3d7a5b336b"
428 }, 428 },
429 "xcompose": { 429 "xcompose": {
430 "cargoLocks": null, 430 "cargoLocks": null,
diff --git a/_sources/generated.nix b/_sources/generated.nix
index e25f1bda..63c464bb 100644
--- a/_sources/generated.nix
+++ b/_sources/generated.nix
@@ -254,15 +254,15 @@
254 }; 254 };
255 v4l2loopback = { 255 v4l2loopback = {
256 pname = "v4l2loopback"; 256 pname = "v4l2loopback";
257 version = "39ad8a43522c18b5e4f4363ce053f604312fc413"; 257 version = "7164d6e6b9aad52a27652c8bb8bd3c3d7a5b336b";
258 src = fetchFromGitHub { 258 src = fetchFromGitHub {
259 owner = "umlaeute"; 259 owner = "umlaeute";
260 repo = "v4l2loopback"; 260 repo = "v4l2loopback";
261 rev = "39ad8a43522c18b5e4f4363ce053f604312fc413"; 261 rev = "7164d6e6b9aad52a27652c8bb8bd3c3d7a5b336b";
262 fetchSubmodules = true; 262 fetchSubmodules = true;
263 sha256 = "sha256-A1p5ZfoMlw6/J3vBdQcXMvERdyBnqs9Ca+0LcLnu7b8="; 263 sha256 = "sha256-1f4+pIbPM/TOJOc7Ns2VDXlBCGyrXiNpmKfThl5kZfk=";
264 }; 264 };
265 date = "2025-01-18"; 265 date = "2025-02-03";
266 }; 266 };
267 xcompose = { 267 xcompose = {
268 pname = "xcompose"; 268 pname = "xcompose";
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
index ee545ba2..abcb80fc 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";
@@ -28,7 +32,11 @@ let
28 32
29 while IFS=$'\n' read -r window_json; do 33 while IFS=$'\n' read -r window_json; do
30 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then 34 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then
31 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")" 35 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then
36 niri msg action focus-workspace-previous
37 else
38 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
39 fi
32 exit 0 40 exit 0
33 fi 41 fi
34 done < <(niri msg -j windows | jq -c '.[]') 42 done < <(niri msg -j windows | jq -c '.[]')
@@ -74,7 +82,7 @@ let
74 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET" 82 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
75 ''; 83 '';
76 }; 84 };
77 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^pwctl|eff|kpxc|bmgr|edit|term$"; 85 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^${lib.concatMapStringsSep "|" ({ name, ...}: name) cfg.scratchspaces}$";
78 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}''; 86 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
79 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}''; 87 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
80 88
@@ -89,7 +97,8 @@ let
89 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")" 97 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
90 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")" 98 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
91 99
92 workspace_json="$(jq -c --arg active_output "$active_output" 'map(select(.output == $active_output and .name == null)) | sort_by(.idx) | .[0]' <<<"$workspaces_json")" 100 history_json="$(socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/niri-workspace-history.sock)"
101 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")"
93 [[ -n $workspace_json && $workspace_json != null ]] || exit 0 102 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
94 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET" 103 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
95 ''; 104 '';
@@ -124,6 +133,54 @@ in {
124 ./swayosd.nix 133 ./swayosd.nix
125 ]; 134 ];
126 135
136 options = {
137 programs.niri.scratchspaces = lib.mkOption {
138 type = lib.types.listOf (lib.types.submodule ({ config, ... }: {
139 options = {
140 name = lib.mkOption {
141 type = lib.types.str;
142 };
143 match = lib.mkOption {
144 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
145 default = [];
146 };
147 exclude = lib.mkOption {
148 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
149 default = [];
150 };
151 windowRuleExtra = lib.mkOption {
152 type = kdl.types.kdl-nodes;
153 default = [];
154 };
155 key = lib.mkOption {
156 type = lib.types.nullOr lib.types.str;
157 default = null;
158 };
159 spawn = lib.mkOption {
160 type = lib.types.nullOr (lib.types.listOf lib.types.str);
161 default = null;
162 };
163 app-id = lib.mkOption {
164 type = lib.types.nullOr lib.types.str;
165 default = null;
166 };
167 selector = lib.mkOption {
168 type = lib.types.nullOr lib.types.str;
169 default = null;
170 };
171 };
172
173 config = lib.mkMerge [
174 (lib.mkIf (config.app-id != null) {
175 match = lib.mkDefault [ { app-id = "^${lib.escapeRegex config.app-id}$"; } ];
176 selector = lib.mkDefault "select(.app_id == \"${config.app-id}\")";
177 })
178 ];
179 }));
180 default = [];
181 };
182 };
183
127 config = { 184 config = {
128 systemd.user.services.xwayland-satellite = { 185 systemd.user.services.xwayland-satellite = {
129 Unit = { 186 Unit = {
@@ -156,474 +213,602 @@ in {
156 ]; 213 ];
157 }; 214 };
158 215
159 programs.niri.settings = { 216 systemd.user.sockets.niri-workspace-history = {
160 prefer-no-csd = true; 217 Socket = {
161 screenshot-path = "${config.home.homeDirectory}/screenshots"; 218 ListenStream = "%t/niri-workspace-history.sock";
162 219 SocketMode = "0600";
163 hotkey-overlay.skip-at-startup = true;
164
165 input = {
166 keyboard = {
167 repeat-delay = 300;
168 repeat-rate = 50;
169
170 xkb = {
171 layout = "us,us";
172 variant = "dvp,";
173 options = "compose:caps,grp:win_space_toggle";
174 };
175 };
176
177 workspace-auto-back-and-forth = true;
178 # focus-follows-mouse.enable = true;
179 warp-mouse-to-focus = true;
180 }; 220 };
181 221 };
182 outputs = { 222 systemd.user.services.niri-workspace-history = {
183 "eDP-1" = { 223 Unit = {
184 scale = 1.5; 224 BindsTo = [ "niri.service" ];
185 position = { x = 0; y = 0; }; 225 After = [ "niri.service" ];
186 };
187 "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" = {
188 scale = 1.5;
189 position = { x = 2560; y = 0; };
190 };
191 "HP Inc. HP 727pu CN4417143K" = {
192 mode = { width = 2560; height = 1440; refresh = 119.998; };
193 scale = 1;
194 position = { x = 2560; y = 0; };
195 variable-refresh-rate = "on-demand";
196 };
197 }; 226 };
198 227 Install = {
199 environment = { 228 WantedBy = [ "niri.service" ];
200 NIXOS_OZONE_WL = "1";
201 QT_QPA_PLATFORM = "wayland";
202 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
203 GDK_BACKEND = "wayland";
204 SDL_VIDEODRIVER = "wayland";
205 DISPLAY = ":0";
206 }; 229 };
207 230 Service = {
208 debug.render-drm-device = "/dev/dri/by-path/pci-0000:00:02.0-render"; 231 Type = "simple";
209 232 Sockets = [ "niri-workspace-history.socket" ];
210 animations = { 233 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" {} ''
211 slowdown = 0.5; 234 import os
235 import socket
236 import json
237 import sys
238 from collections import defaultdict
239 from threading import Thread, Lock
240 from socketserver import StreamRequestHandler, ThreadingTCPServer
241 from contextlib import contextmanager
242 from io import TextIOWrapper
243
244
245 @contextmanager
246 def detaching(thing):
247 try:
248 yield thing
249 finally:
250 thing.detach()
251
252
253 workspace_history = defaultdict(list)
254 history_lock = Lock()
255
256
257 def monitor_niri():
258 workspaces = list()
259
260 def focus_workspace(output, workspace):
261 global workspace_history, history_lock
262
263 with history_lock:
264 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace] # noqa: E501
265 print(json.dumps(workspace_history), file=sys.stderr)
266
267 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
268 sock.connect(os.environ["NIRI_SOCKET"])
269 sock.send(b"\"EventStream\"\n")
270 for line in sock.makefile(buffering=1, encoding='utf-8'):
271 if line_json := json.loads(line):
272 if "WorkspacesChanged" in line_json:
273 workspaces = line_json["WorkspacesChanged"]["workspaces"]
274 for ws in workspaces:
275 if ws["is_focused"]:
276 focus_workspace(ws["output"], ws["id"])
277 if "WorkspaceActivated" in line_json:
278 for ws in workspaces:
279 if ws["id"] != line_json["WorkspaceActivated"]["id"]:
280 continue
281 focus_workspace(ws["output"], ws["id"])
282 break
283
284
285 class RequestHandler(StreamRequestHandler):
286 def handle(self):
287 global workspace_history, history_lock
288
289 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out: # noqa: E501
290 with history_lock:
291 json.dump(workspace_history, out)
292
293
294 class Server(ThreadingTCPServer):
295 def __init__(self):
296 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False) # noqa: E501
297 self.socket = socket.fromfd(3, self.address_family, self.socket_type)
298
299
300 def run_server():
301 with Server() as server:
302 server.serve_forever()
303
304
305 niri = Thread(target=monitor_niri)
306 niri.daemon = True
307 niri.start()
308
309 server_thread = Thread(target=run_server)
310 server_thread.daemon = True
311 server_thread.start()
312
313 while True:
314 server_thread.join(timeout=0.5)
315 niri.join(timeout=0.5)
316
317 if not (niri.is_alive() and server_thread.is_alive()):
318 break
319 '';
212 }; 320 };
321 };
213 322
214 layout = { 323 programs.niri.scratchspaces = [
215 gaps = 8; 324 { name = "pwctl";
216 struts = { left = 0; right = 0; top = 0; bottom = 0; }; 325 key = "Mod+Control+A";
217 focus-ring = { 326 spawn = ["pwvucontrol"];
218 width = 2; 327 app-id = "com.saivert.pwvucontrol";
219 active.gradient = { 328 }
220 from = "hsla(195 100% 60% 0.75)"; 329 { name = "kpxc";
221 to = "hsla(155 100% 50% 0.75)"; 330 exclude = [
222 angle = 29; 331 { title = "^Unlock Database.*"; }
223 relative-to = "workspace-view"; 332 { title = "^Access Request.*"; }
224 }; 333 { title = ".*Passkey credentials$"; }
225 inactive.gradient = {
226 from = "hsla(0 0% 42% 0.66)";
227 to = "hsla(0 0% 35% 0.66)";
228 angle = 29;
229 relative-to = "workspace-view";
230 };
231 };
232
233 preset-column-widths = [
234 { proportion = 1. / 4.; }
235 { proportion = 1. / 3.; }
236 { proportion = 1. / 2.; }
237 { proportion = 2. / 3.; }
238 { proportion = 3. / 4.; }
239 ]; 334 ];
240 default-column-width.proportion = 1. / 2.; 335 windowRuleExtra = [
241 preset-window-heights = [ 336 (kdl.leaf "open-focused" false)
242 { proportion = 1. / 3.; } 337 ];
243 { proportion = 1. / 2.; } 338 key = "Mod+Control+P";
244 { proportion = 2. / 3.; } 339 app-id = "org.keepassxc.KeePassXC";
245 { proportion = 1.; } 340 spawn = [ "keepassxc" ];
341 }
342 { name = "bmgr";
343 key = "Mod+Control+B";
344 app-id = ".blueman-manager-wrapped";
345 spawn = [ "blueman-manager" ];
346 }
347 { name = "term";
348 key = "Mod+Control+Return";
349 app-id = "kitty-scratch";
350 spawn = [ "kitty" "--app-id" "kitty-scratch" ];
351 }
352 { name = "edit";
353 match = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
354 key = "Mod+Control+E";
355 selector = "select(.app_id == \"emacs\" and .title == \"scratch\")";
356 spawn = [ "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))" ];
357 }
358 { name = "eff";
359 key = "Mod+Control+O";
360 app-id = "com.github.wwmm.easyeffects";
361 spawn = [ "easyeffects" ];
362 }
363 ];
364 programs.niri.config =
365 let
366 inherit (kdl) node plain leaf flag;
367 optional-node = cond: v:
368 if cond
369 then v
370 else null;
371 opt-props = lib.filterAttrs (lib.const (value: value != null));
372 in
373 [ (flag "prefer-no-csd")
374
375 (plain "hotkey-overlay" [
376 (flag "skip-at-startup")
377 ])
378
379 (plain "input" [
380 (plain "keyboard" [
381 (leaf "repeat-delay" 300)
382 (leaf "repeat-rate" 50)
383
384 (plain "xkb" [
385 (leaf "layout" "us,us")
386 (leaf "variant" "dvp,")
387 (leaf "options" "compose:caps,grp:win_space_toggle")
388 ])
389 ])
390
391 (flag "workspace-auto-back-and-forth")
392 # (leaf "focus-follows-mouse" {})
393 # (flag "warp-mouse-to-focus")
394
395 (plain "touchpad" [ (flag "off") ])
396 (plain "trackball" [
397 (leaf "scroll-method" "on-button-down")
398 (leaf "scroll-button" 278)
399 ])
400 ])
401
402 (plain "environment" (lib.mapAttrsToList leaf {
403 NIXOS_OZONE_WL = "1";
404 QT_QPA_PLATFORM = "wayland";
405 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
406 GDK_BACKEND = "wayland";
407 SDL_VIDEODRIVER = "wayland";
408 DISPLAY = ":0";
409 }))
410
411 (node "output" "eDP-1" [
412 (leaf "scale" 1.5)
413 (leaf "position" { x = 0; y = 0; })
414 ])
415 (node "output" "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" [
416 (leaf "scale" 1.5)
417 (leaf "position" { x = 2560; y = 0; })
418 ])
419 (node "output" "HP Inc. HP 727pu CN4417143K" [
420 (leaf "mode" "2560x1440@120") # 119.998
421 (leaf "scale" 1)
422 (leaf "position" { x = 2560; y = 0; })
423 (flag "variable-refresh-rate")
424 ])
425
426 (plain "debug" [
427 (leaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render")
428 ])
429
430 (plain "animations" [
431 (leaf "slowdown" 0.5)
432 (plain "workspace-switch" [(flag "off")])
433 ])
434
435 (plain "layout" [
436 (leaf "gaps" 8)
437 (plain "struts" [
438 (leaf "left" 0)
439 (leaf "right" 0)
440 (leaf "top" 0)
441 (leaf "bottom" 0)
442 ])
443 (plain "focus-ring" [
444 (leaf "width" 2)
445 (leaf "active-gradient" {
446 from = "hsla(195 100% 45% 1)";
447 to = "hsla(155 100% 37.5% 1)";
448 angle = 29;
449 relative-to = "workspace-view";
450 })
451 (leaf "inactive-gradient" {
452 from = "hsla(0 0% 27.7% 1)";
453 to = "hsla(0 0% 23% 1)";
454 angle = 29;
455 relative-to = "workspace-view";
456 })
457 ])
458
459 (plain "preset-column-widths" (map (prop: leaf "proportion" prop) [
460 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.)
461 ]))
462 (plain "default-column-width" [ (leaf "proportion" (1. / 2.)) ])
463 (plain "preset-window-heights" (map (prop: leaf "proportion" prop) [
464 (1. / 3.) (1. / 2.) (2. / 3.) (1.)
465 ]))
466
467 (flag "always-center-single-column")
468
469 (plain "tab-indicator" [
470 (leaf "gap" (-6))
471 (leaf "width" 6)
472 (leaf "length" { total-proportion = 1.; })
473 (leaf "active-gradient" {
474 from = "hsla(195 100% 60% 0.75)";
475 to = "hsla(155 100% 50% 0.75)";
476 angle = 29;
477 relative-to = "workspace-view";
478 })
479 (leaf "inactive-gradient" {
480 from = "hsla(0 0% 42% 0.66)";
481 to = "hsla(0 0% 35% 0.66)";
482 angle = 29;
483 relative-to = "workspace-view";
484 })
485 ])
486 ])
487
488 (plain "cursor" [
489 (flag "hide-when-typing")
490 ])
491
492 (map (name:
493 (node "workspace" name [
494 (leaf "open-on-output" "eDP-1")
495 ])
496 ) (map ({name, ...}: name) cfg.scratchspaces))
497 (map (name:
498 (leaf "workspace" name)
499 ) ["comm" "web" "vid" "bmr"])
500
501 (plain "window-rule" [
502 (leaf "match" { is-floating = true; })
503 (leaf "geometry-corner-radius" 8)
504 (leaf "clip-to-geometry" true)
505 ])
506
507 (plain "window-rule" [
508 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; })
509 (leaf "block-out-from" "screencast")
510 ])
511 (plain "window-rule" [
512 (map (title:
513 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; })
514 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$"])
515 (leaf "open-focused" true)
516 (leaf "open-floating" true)
517 ])
518
519 (map ({ name, match, exclude, windowRuleExtra, ... }:
520 (optional-node (match != []) (plain "window-rule" [
521 (map (leaf "match") match)
522 (map (leaf "exclude") exclude)
523 (leaf "open-on-workspace" name)
524 (leaf "open-maximized" true)
525 windowRuleExtra
526 ]))
527 ) cfg.scratchspaces)
528
529 (plain "window-rule" [
530 (leaf "match" { app-id = "^emacs$"; })
531 (leaf "match" { app-id = "^firefox$"; })
532 (plain "default-column-width" [(leaf "proportion" (2. / 3.))])
533 ])
534 (plain "window-rule" [
535 (leaf "match" { app-id = "^kitty$"; })
536 (leaf "match" { app-id = "^kitty-play$"; })
537 (plain "default-column-width" [(leaf "proportion" (1. / 3.))])
538 ])
539
540 (plain "window-rule" [
541 (leaf "match" { app-id = "^thunderbird$"; })
542 (leaf "match" { app-id = "^Element$"; })
543 (leaf "match" { app-id = "^Rainbow$"; })
544 (leaf "open-on-workspace" "comm")
545 ])
546 (plain "window-rule" [
547 (leaf "match" { app-id = "^firefox$"; })
548 (leaf "open-on-workspace" "web")
549 (leaf "open-maximized" true)
550 ])
551 (plain "window-rule" [
552 (leaf "match" { app-id = "^mpv$"; })
553 (leaf "open-on-workspace" "vid")
554 (plain "default-column-width" [(leaf "proportion" 1.)])
555 ])
556 (plain "window-rule" [
557 (leaf "match" { app-id = "^kitty-play$"; })
558 (leaf "open-on-workspace" "vid")
559 (leaf "open-focused" false)
560 ])
561 (plain "window-rule" [
562 (leaf "match" { app-id = "^pdfpc$"; })
563 (plain "default-column-width" [(leaf "proportion" 1.)])
564 ])
565 (plain "window-rule" [
566 (leaf "match" { app-id = "^pdfpc$"; title = "^pdfpc - presentation$"; })
567 (plain "default-column-width" [(leaf "proportion" 1.)])
568 (leaf "open-fullscreen" true)
569 (leaf "open-on-workspace" "bmr")
570 (leaf "open-focused" false)
571 ])
572 (plain "window-rule" [
573 (map (leaf "match") [
574 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
575 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; }
576 { app-id = "^xdg-desktop-portal-gtk$"; }
577 ])
578 (leaf "open-floating" true)
579 ])
580
581 (plain "layer-rule" [
582 (leaf "match" { namespace = "^notifications$"; })
583 (leaf "match" { namespace = "^waybar$"; })
584 (leaf "match" { namespace = "^launcher$"; })
585 (leaf "block-out-from" "screencast")
586 ])
587
588 (plain "binds"
589 (let
590 bind = name: cfg: node name (opt-props {
591 cooldown-ms = cfg.cooldown-ms or null;
592 }
593 // (lib.optionalAttrs (!(cfg.repeat or true)) {
594 repeat = false;
595 })
596 // (lib.optionalAttrs (cfg.allow-when-locked or false) {
597 allow-when-locked = true;
598 })) (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
599 in
600 [
601 (lib.mapAttrsToList bind (with config.lib.niri.actions; {
602 "Mod+Slash".action = show-hotkey-overlay;
603
604 "Mod+Return".action = spawn terminal;
605 "Mod+Q".action = close-window;
606 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
607 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
608
609 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
610 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
611 name = "queue-yt-dlp";
612 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
613 text = ''
614 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
615 '';
616 }));
617 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
618 name = "queue-yt-dlp";
619 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
620 text = ''
621 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
622 '';
623 }));
624
625 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
626 name = "qalc-fuzzel";
627 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
628 text = ''
629 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
630 prev() {
631 FOUND=false
632 while IFS= read -r line; do
633 [[ -n "$line" ]] || continue
634 FOUND=true
635 echo "$line"
636 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)
637 $FOUND || echo
638 }
639 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
640 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
641 QALC_RES="$FUZZEL_RES"
642 QALC_RET=0
643 else
644 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
645 QALC_RET=$?
646 fi
647 [[ -n "$QALC_RES" ]] || exit 1
648 EXISTING=false
649 set +o pipefail
650 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
651 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
652 set -o pipefail
653 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
654 set +o pipefail
655 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
656 set -o pipefail
657 cat >"$RES_FILE" <<<"$QALC_RES"
658 fi
659 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
660 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
661 notify-send "$QALC_RES"
662 '';
663 }));
664 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
665 name = "emoji-fuzzel";
666 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
667 text = ''
668 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
669 [[ -n "$FUZZEL_RES" ]] || exit 1
670 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
671 '';
672 }));
673 "Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
674 name = "screenshot";
675 runtimeInputs = with pkgs; [ grim slurp wl-clipboard-rs coreutils ];
676 text = ''
677 grim -g "$(slurp -b 00000080 -c FFFFFFFF -s 00000000 -w 1)" - \
678 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
679 | wl-copy --type image/png
680 '';
681 }));
682 "Shift+Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
683 name = "screenshot";
684 runtimeInputs = with pkgs; [ grim niri gojq wl-clipboard-rs coreutils ];
685 text = ''
686 grim -o "$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .output')" - \
687 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
688 | wl-copy --type image/png
689 '';
690 }));
691 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
692 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
693
694 "Mod+H".action = focus-column-left;
695 "Mod+T".action = focus-window-down;
696 "Mod+N".action = focus-window-up;
697 "Mod+S".action = focus-column-right;
698
699 "Mod+Shift+H".action = move-column-left;
700 "Mod+Shift+T".action = move-window-down;
701 "Mod+Shift+N".action = move-window-up;
702 "Mod+Shift+S".action = move-column-right;
703
704 "Mod+Control+H".action = focus-monitor-left;
705 "Mod+Control+T".action = focus-monitor-down;
706 "Mod+Control+N".action = focus-monitor-up;
707 "Mod+Control+S".action = focus-monitor-right;
708
709 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
710 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
711 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
712 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
713
714 "Mod+G".action = focus-adjacent-workspace "down";
715 "Mod+C".action = focus-adjacent-workspace "up";
716
717 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
718 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
719
720 "Mod+Shift+Control+G".action = move-workspace-down;
721 "Mod+Shift+Control+C".action = move-workspace-up;
722
723 "Mod+ParenLeft".action = focus-workspace "comm";
724 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
725
726 "Mod+ParenRight".action = focus-workspace "web";
727 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
728
729 "Mod+BraceRight".action = focus-workspace "read";
730 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
731
732 "Mod+BraceLeft".action = focus-workspace "mon";
733 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
734
735 "Mod+Asterisk".action = focus-workspace "vid";
736 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
737
738 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
739 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
740
741 "Mod+M".action = consume-or-expel-window-left;
742 "Mod+W".action = consume-or-expel-window-right;
743
744 "Mod+Shift+M".action = toggle-column-tabbed-display;
745
746 "Mod+R".action = switch-preset-column-width;
747 "Mod+Shift+R".action = switch-preset-window-height;
748 "Mod+F".action = center-column;
749 "Mod+Shift+F".action = maximize-column;
750 "Mod+Shift+Ctrl+F".action = fullscreen-window;
751
752 "Mod+V".action = switch-focus-between-floating-and-tiling;
753 "Mod+Shift+V".action = toggle-window-floating;
754
755 "Mod+Left".action = set-column-width "-10%";
756 "Mod+Down".action = set-window-height "-10%";
757 "Mod+Up".action = set-window-height "+10%";
758 "Mod+Right".action = set-column-width "+10%";
759
760 "Mod+Shift+Z" = {
761 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
762 allow-when-locked = true;
763 };
764 "Mod+Shift+L".action = spawn loginctl "lock-session";
765 "Mod+Shift+E".action = quit;
766 "Mod+Shift+Minus" = {
767 action = spawn systemctl "suspend";
768 allow-when-locked = true;
769 };
770 "Mod+Shift+Control+Minus" = {
771 action = spawn systemctl "hibernate";
772 allow-when-locked = true;
773 };
774 "Mod+Shift+P" = {
775 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
776 allow-when-locked = true;
777 };
778
779 "XF86MonBrightnessUp" = {
780 action = spawn swayosd-client "--brightness" "raise";
781 allow-when-locked = true;
782 };
783 "XF86MonBrightnessDown" = {
784 action = spawn swayosd-client "--brightness" "lower";
785 allow-when-locked = true;
786 };
787 "XF86AudioRaiseVolume" = {
788 action = spawn swayosd-client "--output-volume" "raise";
789 allow-when-locked = true;
790 };
791 "XF86AudioLowerVolume" = {
792 action = spawn swayosd-client "--output-volume" "lower";
793 allow-when-locked = true;
794 };
795 "XF86AudioMute" = {
796 action = spawn swayosd-client "--output-volume" "mute-toggle";
797 allow-when-locked = true;
798 };
799 "XF86AudioMicMute" = {
800 action = spawn swayosd-client "--input-volume" "mute-toggle";
801 allow-when-locked = true;
802 };
803
804 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
805 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
806 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
807 "Mod+Comma".action = spawn makoctl "restore";
808 }))
809 (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)
810 ]
811 ))
246 ]; 812 ];
247
248 always-center-single-column = true;
249 };
250
251 cursor.hide-when-typing = true;
252
253 input = {
254 touchpad.enable = false;
255 trackball = {
256 scroll-method = "on-button-down";
257 scroll-button = 278;
258 };
259 };
260
261 workspaces = {
262 "001" = { name = "pwctl"; open-on-output = "eDP-1"; };
263 "002" = { name = "kpxc"; open-on-output = "eDP-1"; };
264 "003" = { name = "bmgr"; open-on-output = "eDP-1"; };
265 "004" = { name = "term"; open-on-output = "eDP-1"; };
266 "005" = { name = "edit"; open-on-output = "eDP-1"; };
267 "006" = { name = "eff"; open-on-output = "eDP-1"; };
268 "101".name = "comm";
269 "102".name = "web";
270 # "104".name = "read";
271 # "105".name = "mon";
272 "110".name = "vid";
273 "120".name = "bmr";
274 };
275
276 window-rules = [
277 {
278 matches = [ { is-floating = true; } ];
279 geometry-corner-radius =
280 let
281 allCorners = r: { bottom-left = r; bottom-right = r; top-left = r; top-right = r; };
282 in allCorners 8.;
283 clip-to-geometry = true;
284 }
285 {
286 matches = [ { app-id = "^com\.saivert\.pwvucontrol$"; } ];
287 open-on-workspace = "pwctl";
288 open-maximized = true;
289 }
290 {
291 matches = [ { app-id = "^com\.github\.wwmm\.easyeffects$"; } ];
292 open-on-workspace = "eff";
293 open-maximized = true;
294 }
295 {
296 matches = [ { app-id = "^\.blueman-manager-wrapped$"; } ];
297 open-on-workspace = "bmgr";
298 open-maximized = true;
299 }
300 {
301 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
302 block-out-from = "screencast";
303 }
304 {
305 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ];
306 excludes = [
307 { title = "^Unlock Database.*"; }
308 { title = "^Access Request.*"; }
309 { title = ".*Passkey credentials$"; }
310 ];
311 open-on-workspace = "kpxc";
312 open-maximized = true;
313 open-focused = false;
314 }
315 {
316 matches = [
317 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Unlock Database.*"; }
318 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Access Request.*"; }
319 { app-id = "^org\.keepassxc\.KeePassXC$"; title = ".*Passkey credentials$"; }
320 ];
321 open-focused = true;
322 open-floating = true;
323 }
324 {
325 matches = [ { app-id = "^kitty-scratch$"; } ];
326 open-on-workspace = "term";
327 open-maximized = true;
328 }
329 {
330 matches = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
331 open-on-workspace = "edit";
332 open-maximized = true;
333 }
334 {
335 matches = [
336 { app-id = "^emacs$"; }
337 { app-id = "^firefox$"; }
338 ];
339 default-column-width.proportion = 2. / 3.;
340 }
341 {
342 matches = [
343 { app-id = "^kitty$"; }
344 { app-id = "^kitty-play$"; }
345 ];
346 default-column-width.proportion = 1. / 3.;
347 }
348 {
349 matches = [
350 { app-id = "^thunderbird$"; }
351 { app-id = "^Element$"; }
352 { app-id = "^Rainbow$"; }
353 ];
354 open-on-workspace = "comm";
355 }
356 {
357 matches = [ { app-id = "^firefox$"; } ];
358 open-on-workspace = "web";
359 open-maximized = true;
360 variable-refresh-rate = true;
361 }
362 # {
363 # matches = [
364 # { app-id = "^evince$"; }
365 # { app-id = "^imv$"; }
366 # { app-id = "^org\.pwmt\.zathura$"; }
367 # ];
368 # open-on-workspace = "read";
369 # }
370 {
371 matches = [ { app-id = "^mpv$"; } ];
372 open-on-workspace = "vid";
373 default-column-width.proportion = 1.;
374 variable-refresh-rate = true;
375 }
376 {
377 matches = [ { app-id = "^kitty-play$"; } ];
378 open-on-workspace = "vid";
379 open-focused = false;
380 }
381 # {
382 # matches = [
383 # { app-id = "^qemu$"; }
384 # { app-id = "^virt-manager$"; }
385 # ];
386 # open-on-workspace = "mon";
387 # }
388 {
389 matches = [ { app-id = "^pdfpc$"; } ];
390 default-column-width.proportion = 1.;
391 }
392 {
393 matches = [ { app-id = "^pdfpc$"; title = "^pdfpc - presentation"; } ];
394 open-on-workspace = "bmr";
395 open-fullscreen = true;
396 }
397 {
398 matches = [
399 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
400 { app-id = "^org\.kde\.polkit-kde-authentication-agent-1$"; }
401 ];
402 open-floating = true;
403 }
404 ];
405 layer-rules = [
406 { matches = [
407 { namespace = "^notifications$"; }
408 { namespace = "^waybar$"; }
409 ];
410 block-out-from = "screencast";
411 }
412 ];
413
414 binds = with config.lib.niri.actions; {
415 "Mod+Slash".action = show-hotkey-overlay;
416
417 "Mod+Return".action = spawn terminal;
418 "Mod+Q".action = close-window;
419 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
420 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
421
422 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
423 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
424 name = "queue-yt-dlp";
425 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
426 text = ''
427 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
428 '';
429 }));
430 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
431 name = "queue-yt-dlp";
432 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
433 text = ''
434 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
435 '';
436 }));
437
438 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
439 name = "qalc-fuzzel";
440 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
441 text = ''
442 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
443 prev() {
444 FOUND=false
445 while IFS= read -r line; do
446 [[ -n "$line" ]] || continue
447 FOUND=true
448 echo "$line"
449 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)
450 $FOUND || echo
451 }
452 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
453 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
454 QALC_RES="$FUZZEL_RES"
455 QALC_RET=0
456 else
457 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
458 QALC_RET=$?
459 fi
460 [[ -n "$QALC_RES" ]] || exit 1
461 EXISTING=false
462 set +o pipefail
463 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
464 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
465 set -o pipefail
466 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
467 set +o pipefail
468 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
469 set -o pipefail
470 cat >"$RES_FILE" <<<"$QALC_RES"
471 fi
472 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
473 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
474 notify-send "$QALC_RES"
475 '';
476 }));
477 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
478 name = "emoji-fuzzel";
479 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
480 text = ''
481 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
482 [[ -n "$FUZZEL_RES" ]] || exit 1
483 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
484 '';
485 }));
486 "Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
487 name = "screenshot";
488 runtimeInputs = with pkgs; [ grim slurp wl-clipboard-rs coreutils ];
489 text = ''
490 grim -g "$(slurp -b 00000080 -c FFFFFFFF -s 00000000 -w 1)" - \
491 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
492 | wl-copy --type image/png
493 '';
494 }));
495 "Shift+Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
496 name = "screenshot";
497 runtimeInputs = with pkgs; [ grim niri gojq wl-clipboard-rs coreutils ];
498 text = ''
499 grim -o "$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .output')" - \
500 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
501 | wl-copy --type image/png
502 '';
503 }));
504 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
505 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
506
507 "Mod+H".action = focus-column-left;
508 "Mod+T".action = focus-window-down;
509 "Mod+N".action = focus-window-up;
510 "Mod+S".action = focus-column-right;
511
512 "Mod+Shift+H".action = move-column-left;
513 "Mod+Shift+T".action = move-window-down;
514 "Mod+Shift+N".action = move-window-up;
515 "Mod+Shift+S".action = move-column-right;
516
517 "Mod+Control+H".action = focus-monitor-left;
518 "Mod+Control+T".action = focus-monitor-down;
519 "Mod+Control+N".action = focus-monitor-up;
520 "Mod+Control+S".action = focus-monitor-right;
521
522 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
523 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
524 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
525 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
526
527 "Mod+G".action = focus-adjacent-workspace "down";
528 "Mod+C".action = focus-adjacent-workspace "up";
529
530 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
531 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
532
533 "Mod+Shift+Control+G".action = move-workspace-down;
534 "Mod+Shift+Control+C".action = move-workspace-up;
535
536 "Mod+ParenLeft".action = focus-workspace "comm";
537 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
538
539 "Mod+ParenRight".action = focus-workspace "web";
540 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
541
542 "Mod+BraceRight".action = focus-workspace "read";
543 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
544
545 "Mod+BraceLeft".action = focus-workspace "mon";
546 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
547
548 "Mod+Asterisk".action = focus-workspace "vid";
549 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
550
551 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
552 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
553
554 "Mod+M".action = consume-or-expel-window-left;
555 "Mod+W".action = consume-or-expel-window-right;
556
557 "Mod+R".action = switch-preset-column-width;
558 "Mod+Shift+R".action = switch-preset-window-height;
559 "Mod+F".action = center-column;
560 "Mod+Shift+F".action = maximize-column;
561 "Mod+Shift+Ctrl+F".action = fullscreen-window;
562
563 "Mod+V".action = switch-focus-between-floating-and-tiling;
564 "Mod+Shift+V".action = toggle-window-floating;
565
566 "Mod+Left".action = set-column-width "-10%";
567 "Mod+Down".action = set-window-height "-10%";
568 "Mod+Up".action = set-window-height "+10%";
569 "Mod+Right".action = set-column-width "+10%";
570
571 "Mod+Shift+Z" = {
572 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
573 allow-when-locked = true;
574 };
575 "Mod+Shift+L".action = spawn loginctl "lock-session";
576 "Mod+Shift+E".action = quit;
577 "Mod+Shift+Minus" = {
578 action = spawn systemctl "suspend";
579 allow-when-locked = true;
580 };
581 "Mod+Shift+Control+Minus" = {
582 action = spawn systemctl "hibernate";
583 allow-when-locked = true;
584 };
585 "Mod+Shift+P" = {
586 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
587 allow-when-locked = true;
588 };
589
590 "XF86MonBrightnessUp" = {
591 action = spawn swayosd-client "--brightness" "raise";
592 allow-when-locked = true;
593 };
594 "XF86MonBrightnessDown" = {
595 action = spawn swayosd-client "--brightness" "lower";
596 allow-when-locked = true;
597 };
598 "XF86AudioRaiseVolume" = {
599 action = spawn swayosd-client "--output-volume" "raise";
600 allow-when-locked = true;
601 };
602 "XF86AudioLowerVolume" = {
603 action = spawn swayosd-client "--output-volume" "lower";
604 allow-when-locked = true;
605 };
606 "XF86AudioMute" = {
607 action = spawn swayosd-client "--output-volume" "mute-toggle";
608 allow-when-locked = true;
609 };
610 "XF86AudioMicMute" = {
611 action = spawn swayosd-client "--input-volume" "mute-toggle";
612 allow-when-locked = true;
613 };
614
615 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
616 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
617 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
618 "Mod+Comma".action = spawn makoctl "restore";
619
620 "Mod+Control+A".action = focus-or-spawn-action-app_id "com.saivert.pwvucontrol" "pwctl" "pwvucontrol";
621 "Mod+Control+O".action = focus-or-spawn-action-app_id "com.github.wwmm.easyeffects" "eff" "easyeffects";
622 "Mod+Control+P".action = focus-or-spawn-action-app_id "org.keepassxc.KeePassXC" "kpxc" "keepassxc";
623 "Mod+Control+B".action = focus-or-spawn-action-app_id ".blueman-manager-wrapped" "bmgr" "blueman-manager";
624 "Mod+Control+Return".action = focus-or-spawn-action-app_id "kitty-scratch" "term" "kitty" "--app-id" "kitty-scratch";
625 "Mod+Control+E".action = focus-or-spawn-action "select(.app_id == \"emacs\" and .title == \"scratch\")" "edit" "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))";
626 };
627 };
628 }; 813 };
629} 814}
diff --git a/accounts/gkleen@sif/niri/waybar.nix b/accounts/gkleen@sif/niri/waybar.nix
index 3f1f8119..bae818f6 100644
--- a/accounts/gkleen@sif/niri/waybar.nix
+++ b/accounts/gkleen@sif/niri/waybar.nix
@@ -131,7 +131,7 @@ in {
131 return-type = "json"; 131 return-type = "json";
132 }; 132 };
133 "niri/workspaces" = { 133 "niri/workspaces" = {
134 ignore = ["eff" "pwctl" "kpxc" "bmgr" "edit" "term"]; 134 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
135 }; 135 };
136 "niri/window" = { 136 "niri/window" = {
137 separate-outputs = true; 137 separate-outputs = true;
@@ -217,7 +217,7 @@ in {
217 modules-right = [ "clock" ]; 217 modules-right = [ "clock" ];
218 218
219 "niri/workspaces" = { 219 "niri/workspaces" = {
220 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"]; 220 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
221 }; 221 };
222 "niri/window" = { 222 "niri/window" = {
223 separate-outputs = true; 223 separate-outputs = true;
diff --git a/flake.lock b/flake.lock
index 876ebecf..0889ba37 100644
--- a/flake.lock
+++ b/flake.lock
@@ -322,11 +322,11 @@
322 ] 322 ]
323 }, 323 },
324 "locked": { 324 "locked": {
325 "lastModified": 1737831749, 325 "lastModified": 1738691953,
326 "narHash": "sha256-La1xZYZ1yHvT4h5MNl5WC2wxBi2P4vozce2n7V/9+2w=", 326 "narHash": "sha256-JY/w2Xyrj3mhUhLJkSgk8t7MSf3LGZjewPTQ7QtCbHE=",
327 "owner": "gkleen", 327 "owner": "gkleen",
328 "repo": "home-manager", 328 "repo": "home-manager",
329 "rev": "8b16ee252e38acc29ba634ab60672a051ebc9f59", 329 "rev": "c077fc8684289ab1b1c2231bab1566879e099c97",
330 "type": "github" 330 "type": "github"
331 }, 331 },
332 "original": { 332 "original": {
@@ -397,11 +397,11 @@
397 "xwayland-satellite-unstable": "xwayland-satellite-unstable" 397 "xwayland-satellite-unstable": "xwayland-satellite-unstable"
398 }, 398 },
399 "locked": { 399 "locked": {
400 "lastModified": 1737961005, 400 "lastModified": 1738770770,
401 "narHash": "sha256-b4hqJNgyx8lnngz7NFcJ1W+59xQnMQYF0EK5g0IOy7c=", 401 "narHash": "sha256-nfapp7C4BbdvHTRA1HSRVYjD5Fk2FCKoyxQLzcL5X50=",
402 "owner": "sodiboo", 402 "owner": "sodiboo",
403 "repo": "niri-flake", 403 "repo": "niri-flake",
404 "rev": "e98ae62893568dd31e7a7e4e75e1dbbf23f759a0", 404 "rev": "83abbde7c8164ee4b42a8647e4e61015c3f45816",
405 "type": "github" 405 "type": "github"
406 }, 406 },
407 "original": { 407 "original": {
@@ -431,15 +431,15 @@
431 "niri-unstable": { 431 "niri-unstable": {
432 "flake": false, 432 "flake": false,
433 "locked": { 433 "locked": {
434 "lastModified": 1737795105, 434 "lastModified": 1738765557,
435 "narHash": "sha256-OsrjQ8O9t9NjDCwfG/EY8MT+K3lb+A5U6SZZ+4PyKzk=", 435 "narHash": "sha256-Ea/iYsAq0qpBWybbvfJKIvd1On9HhGkBE3oNyJGslUs=",
436 "owner": "gkleen", 436 "owner": "yalter",
437 "repo": "niri", 437 "repo": "niri",
438 "rev": "78697d1cea20e6b53013e820999b0403c45d9f00", 438 "rev": "f4e9732a6b940368af9985b238cc12698b4e38a9",
439 "type": "github" 439 "type": "github"
440 }, 440 },
441 "original": { 441 "original": {
442 "owner": "gkleen", 442 "owner": "YaLTeR",
443 "repo": "niri", 443 "repo": "niri",
444 "type": "github" 444 "type": "github"
445 } 445 }
@@ -472,11 +472,11 @@
472 ] 472 ]
473 }, 473 },
474 "locked": { 474 "locked": {
475 "lastModified": 1737861961, 475 "lastModified": 1738466368,
476 "narHash": "sha256-LIRtMvAwLGb8pBoamzgEF67oKlNPz4LuXiRPVZf+TpE=", 476 "narHash": "sha256-PZhUjtvQZOH3PO0EYdTpQvcqkgkq1NkP2A6w9SPHYsk=",
477 "owner": "Mic92", 477 "owner": "Mic92",
478 "repo": "nix-index-database", 478 "repo": "nix-index-database",
479 "rev": "79b7b8eae3243fc5aa9aad34ba6b9bbb2266f523", 479 "rev": "46a8f5fc9552b776bfc5c5c96ea3bede33f68f52",
480 "type": "github" 480 "type": "github"
481 }, 481 },
482 "original": { 482 "original": {
@@ -508,11 +508,11 @@
508 }, 508 },
509 "nixos-hardware": { 509 "nixos-hardware": {
510 "locked": { 510 "locked": {
511 "lastModified": 1737751639, 511 "lastModified": 1738638143,
512 "narHash": "sha256-ZEbOJ9iT72iwqXsiEMbEa8wWjyFvRA9Ugx8utmYbpz4=", 512 "narHash": "sha256-ZYMe4c4OCtIUBn5hx15PEGr0+B1cNEpl2dsaLxwY2W0=",
513 "owner": "NixOS", 513 "owner": "NixOS",
514 "repo": "nixos-hardware", 514 "repo": "nixos-hardware",
515 "rev": "dfad538f751a5aa5d4436d9781ab27a6128ec9d4", 515 "rev": "9bdd53f5908453e4d03f395eb1615c3e9a351f70",
516 "type": "github" 516 "type": "github"
517 }, 517 },
518 "original": { 518 "original": {
@@ -630,11 +630,11 @@
630 }, 630 },
631 "nixpkgs-stable_2": { 631 "nixpkgs-stable_2": {
632 "locked": { 632 "locked": {
633 "lastModified": 1737885640, 633 "lastModified": 1738702386,
634 "narHash": "sha256-GFzPxJzTd1rPIVD4IW+GwJlyGwBDV1Tj5FLYwDQQ9sM=", 634 "narHash": "sha256-nJj8f78AYAxl/zqLiFGXn5Im1qjFKU8yBPKoWEeZN5M=",
635 "owner": "NixOS", 635 "owner": "NixOS",
636 "repo": "nixpkgs", 636 "repo": "nixpkgs",
637 "rev": "4e96537f163fad24ed9eb317798a79afc85b51b7", 637 "rev": "030ba1976b7c0e1a67d9716b17308ccdab5b381e",
638 "type": "github" 638 "type": "github"
639 }, 639 },
640 "original": { 640 "original": {
@@ -678,11 +678,11 @@
678 }, 678 },
679 "nixpkgs_2": { 679 "nixpkgs_2": {
680 "locked": { 680 "locked": {
681 "lastModified": 1737885589, 681 "lastModified": 1738680400,
682 "narHash": "sha256-Zf0hSrtzaM1DEz8//+Xs51k/wdSajticVrATqDrfQjg=", 682 "narHash": "sha256-ooLh+XW8jfa+91F1nhf9OF7qhuA/y1ChLx6lXDNeY5U=",
683 "owner": "NixOS", 683 "owner": "NixOS",
684 "repo": "nixpkgs", 684 "repo": "nixpkgs",
685 "rev": "852ff1d9e153d8875a83602e03fdef8a63f0ecf8", 685 "rev": "799ba5bffed04ced7067a91798353d360788b30d",
686 "type": "github" 686 "type": "github"
687 }, 687 },
688 "original": { 688 "original": {
@@ -748,11 +748,11 @@
748 "treefmt-nix": "treefmt-nix" 748 "treefmt-nix": "treefmt-nix"
749 }, 749 },
750 "locked": { 750 "locked": {
751 "lastModified": 1736884309, 751 "lastModified": 1738741221,
752 "narHash": "sha256-eiCqmKl0BIRiYk5/ZhZozwn4/7Km9CWTbc15Cv+VX5k=", 752 "narHash": "sha256-UiTOA89yQV5YNlO1ZAp4IqJUGWOnTyBC83netvt8rQE=",
753 "owner": "nix-community", 753 "owner": "nix-community",
754 "repo": "poetry2nix", 754 "repo": "poetry2nix",
755 "rev": "75d0515332b7ca269f6d7abfd2c44c47a7cbca7b", 755 "rev": "be1fe795035d3d36359ca9135b26dcc5321b31fb",
756 "type": "github" 756 "type": "github"
757 }, 757 },
758 "original": { 758 "original": {
@@ -891,11 +891,11 @@
891 ] 891 ]
892 }, 892 },
893 "locked": { 893 "locked": {
894 "lastModified": 1737411508, 894 "lastModified": 1738291974,
895 "narHash": "sha256-j9IdflJwRtqo9WpM0OfAZml47eBblUHGNQTe62OUqTw=", 895 "narHash": "sha256-wkwYJc8cKmmQWUloyS9KwttBnja2ONRuJQDEsmef320=",
896 "owner": "Mic92", 896 "owner": "Mic92",
897 "repo": "sops-nix", 897 "repo": "sops-nix",
898 "rev": "015d461c16678fc02a2f405eb453abb509d4e1d4", 898 "rev": "4c1251904d8a08c86ac6bc0d72cc09975e89aef7",
899 "type": "github" 899 "type": "github"
900 }, 900 },
901 "original": { 901 "original": {
diff --git a/flake.nix b/flake.nix
index 5cc1e298..4e119e98 100644
--- a/flake.nix
+++ b/flake.nix
@@ -191,7 +191,7 @@
191 ref = "main"; 191 ref = "main";
192 inputs = { 192 inputs = {
193 nixpkgs.follows = "nixpkgs"; 193 nixpkgs.follows = "nixpkgs";
194 niri-unstable.url = "github:gkleen/niri"; 194 # niri-unstable.url = "github:gkleen/niri";
195 }; 195 };
196 }; 196 };
197 }; 197 };
diff --git a/hosts/sif/default.nix b/hosts/sif/default.nix
index 32651e14..2a3a6be9 100644
--- a/hosts/sif/default.nix
+++ b/hosts/sif/default.nix
@@ -477,10 +477,10 @@ in {
477 systemd.tmpfiles.settings = { 477 systemd.tmpfiles.settings = {
478 "10-localtime"."/etc/localtime".L.argument = "/.bcachefs/etc/localtime"; 478 "10-localtime"."/etc/localtime".L.argument = "/.bcachefs/etc/localtime";
479 479
480 "10-regreet"."/var/cache/regreet/cache.toml".C.argument = toString ((pkgs.formats.toml {}).generate "cache.toml" { 480 # "10-regreet"."/var/cache/regreet/cache.toml".C.argument = toString ((pkgs.formats.toml {}).generate "cache.toml" {
481 last_user = "gkleen"; 481 # last_user = "gkleen";
482 user_to_last_sess.gkleen = "niri"; 482 # user_to_last_sess.gkleen = "Niri";
483 }); 483 # });
484 }; 484 };
485 485
486 users = { 486 users = {
diff --git a/hosts/sif/greetd/default.nix b/hosts/sif/greetd/default.nix
index f609fc05..37ca13c5 100644
--- a/hosts/sif/greetd/default.nix
+++ b/hosts/sif/greetd/default.nix
@@ -11,6 +11,11 @@
11 # exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} ${lib.escapeShellArgs cfg.cageArgs} -- ${lib.getExe cfg.package} 11 # exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} ${lib.escapeShellArgs cfg.cageArgs} -- ${lib.getExe cfg.package}
12 # ''; 12 # '';
13 }; 13 };
14 systemd.services.greetd.environment = {
15 XKB_DEFAULT_LAYOUT = "us,us";
16 XKB_DEFAULT_VARIANT = "dvp,";
17 XKB_DEFAULT_OPTIONS = "compose:caps,grp:win_space_toggle";
18 };
14 programs.regreet = { 19 programs.regreet = {
15 enable = true; 20 enable = true;
16 theme = { 21 theme = {
diff --git a/overlays/swayosd.nix b/overlays/swayosd/default.nix
index 61c865e7..28c8f1b9 100644
--- a/overlays/swayosd.nix
+++ b/overlays/swayosd/default.nix
@@ -23,5 +23,8 @@
23 udev 23 udev
24 sassc 24 sassc
25 ]; 25 ];
26 patches = (oldAttrs.patches or []) ++ [
27 ./exponential.patch
28 ];
26 }); 29 });
27} 30}
diff --git a/overlays/swayosd/exponential.patch b/overlays/swayosd/exponential.patch
new file mode 100644
index 00000000..eb90d739
--- /dev/null
+++ b/overlays/swayosd/exponential.patch
@@ -0,0 +1,57 @@
1diff --git a/src/brightness_backend/brightnessctl.rs b/src/brightness_backend/brightnessctl.rs
2index ccb0e11..740fdb6 100644
3--- a/src/brightness_backend/brightnessctl.rs
4+++ b/src/brightness_backend/brightnessctl.rs
5@@ -107,10 +107,21 @@ impl VirtualDevice {
6 }
7 }
8
9- fn set_percent(&mut self, mut val: u32) -> anyhow::Result<()> {
10- val = val.clamp(0, 100);
11- self.current = self.max.map(|max| val * max / 100);
12- let _: String = self.run(("set", &*format!("{val}%")))?;
13+ fn val_to_percent(&mut self, val: u32) -> u32 {
14+ return ((val as f64 / self.get_max() as f64).powf(0.25) * 100_f64).round() as u32;
15+ }
16+ fn percent_to_val(&mut self, perc: u32) -> u32 {
17+ return ((perc as f64 / 100_f64).powf(4_f64) * self.get_max() as f64).round() as u32;
18+ }
19+
20+ fn set_percent(&mut self, val: u32) -> anyhow::Result<()> {
21+ let new = self.percent_to_val(val);
22+ self.set_val(new)
23+ }
24+ fn set_val(&mut self, val: u32) -> anyhow::Result<()> {
25+ let curr = val.clamp(0, self.get_max());
26+ self.current = Some(curr);
27+ let _: String = self.run(("set", &*format!("{curr}")))?;
28 Ok(())
29 }
30 }
31@@ -134,20 +145,18 @@ impl BrightnessBackend for BrightnessCtl {
32
33 fn lower(&mut self, by: u32) -> anyhow::Result<()> {
34 let curr = self.get_current();
35- let max = self.get_max();
36-
37- let curr = curr * 100 / max;
38+ let mut new = self.device.val_to_percent(curr).saturating_sub(by);
39+ new = self.device.percent_to_val(new).min(curr.saturating_sub(1));
40
41- self.device.set_percent(curr.saturating_sub(by))
42+ self.device.set_val(new)
43 }
44
45 fn raise(&mut self, by: u32) -> anyhow::Result<()> {
46 let curr = self.get_current();
47- let max = self.get_max();
48-
49- let curr = curr * 100 / max;
50+ let mut new = self.device.val_to_percent(curr) + by;
51+ new = self.device.percent_to_val(new).max(curr + 1);
52
53- self.device.set_percent(curr + by)
54+ self.device.set_val(new)
55 }
56
57 fn set(&mut self, val: u32) -> anyhow::Result<()> {
diff --git a/overlays/worktime/worktime/__main__.py b/overlays/worktime/worktime/__main__.py
index 9335afdc..ba6c5ff6 100755
--- a/overlays/worktime/worktime/__main__.py
+++ b/overlays/worktime/worktime/__main__.py
@@ -224,6 +224,7 @@ class Worktime(object):
224 leave_budget = dict() 224 leave_budget = dict()
225 time_per_day = None 225 time_per_day = None
226 workdays = None 226 workdays = None
227 pull_forward = dict()
227 228
228 @staticmethod 229 @staticmethod
229 @cache 230 @cache
@@ -391,8 +392,6 @@ class Worktime(object):
391 if e.errno != 2: 392 if e.errno != 2:
392 raise e 393 raise e
393 394
394 pull_forward = dict()
395
396 start_day = self.start_date.date() 395 start_day = self.start_date.date()
397 end_day = self.end_date.date() 396 end_day = self.end_date.date()
398 397
@@ -419,15 +418,15 @@ class Worktime(object):
419 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break 418 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break
420 else: 419 else:
421 if d >= self.end_date.date(): 420 if d >= self.end_date.date():
422 pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day(d) - (holidays[d] if d in holidays else timedelta())) 421 self.pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day(d) - (holidays[d] if d in holidays else timedelta()))
423 except IOError as e: 422 except IOError as e:
424 if e.errno != 2: 423 if e.errno != 2:
425 raise e 424 raise e
426 425
427 self.days_to_work = dict() 426 self.days_to_work = dict()
428 427
429 if pull_forward: 428 if self.pull_forward:
430 end_day = max(end_day, max(list(pull_forward))) 429 end_day = max(end_day, max(list(self.pull_forward)))
431 430
432 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]: 431 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]:
433 if day.isoweekday() in self.workdays: 432 if day.isoweekday() in self.workdays:
@@ -471,17 +470,17 @@ class Worktime(object):
471 self.extra_days_to_work[self.now.date()] = timedelta() 470 self.extra_days_to_work[self.now.date()] = timedelta()
472 471
473 self.time_to_work = sum([self.days_to_work[day] for day in self.days_to_work.keys() if day <= self.end_date.date()], timedelta()) 472 self.time_to_work = sum([self.days_to_work[day] for day in self.days_to_work.keys() if day <= self.end_date.date()], timedelta())
474 for day in [d for d in list(pull_forward) if d > self.end_date.date()]: 473 for day in [d for d in list(self.pull_forward) if d > self.end_date.date()]:
475 days_forward = set([d for d in self.days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in pull_forward or d == self.end_date.date())]) 474 days_forward = set([d for d in self.days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in self.pull_forward or d == self.end_date.date())])
476 extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in pull_forward or d == self.end_date.date())]) 475 extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in self.pull_forward or d == self.end_date.date())])
477 days_forward = days_forward.union(extra_days_forward) 476 days_forward = days_forward.union(extra_days_forward)
478 477
479 extra_day_time_left = timedelta() 478 extra_day_time_left = timedelta()
480 for extra_day in extra_days_forward: 479 for extra_day in extra_days_forward:
481 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day]) 480 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day])
482 extra_day_time_left += day_time 481 extra_day_time_left += day_time
483 extra_day_time = min(extra_day_time_left, pull_forward[day]) 482 extra_day_time = min(extra_day_time_left, self.pull_forward[day])
484 time_forward = pull_forward[day] - extra_day_time 483 time_forward = self.pull_forward[day] - extra_day_time
485 if extra_day_time_left > timedelta(): 484 if extra_day_time_left > timedelta():
486 for extra_day in extra_days_forward: 485 for extra_day in extra_days_forward:
487 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day]) 486 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day])
@@ -571,7 +570,10 @@ def worktime(pull_forward_cutoff, waybar, **args):
571 return f"({difference_string})" 570 return f"({difference_string})"
572 571
573 out_class = "running" if worktime.running_entry else "stopped" 572 out_class = "running" if worktime.running_entry else "stopped"
574 tooltip = tooltip_timedelta(worktime.time_to_work - worktime.time_worked) 573 difference = worktime.time_to_work - worktime.time_worked
574 if worktime.running_entry and -min(timedelta(milliseconds=0), difference) > sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0)) or not worktime.running_entry and max(timedelta(milliseconds=0), difference) > worktime.time_per_day(worktime.now.date()) and worktime.now_is_workday:
575 out_class = "over"
576 tooltip = tooltip_timedelta(difference)
575 if worktime.time_pulled_forward >= min(pull_forward_cutoff, timedelta(seconds = 1)): 577 if worktime.time_pulled_forward >= min(pull_forward_cutoff, timedelta(seconds = 1)):
576 worktime_no_pulled_forward = deepcopy(worktime) 578 worktime_no_pulled_forward = deepcopy(worktime)
577 worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward 579 worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward
@@ -593,6 +595,10 @@ def worktime(pull_forward_cutoff, waybar, **args):
593 else: 595 else:
594 print(out_text) 596 print(out_text)
595 597
598def pull_forward(**args):
599 worktime = Worktime(**args)
600 print(tooltip_timedelta(sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0))))
601
596def time_worked(now, waybar, **args): 602def time_worked(now, waybar, **args):
597 then = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0) 603 then = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0)
598 if now.time() == time(): 604 if now.time() == time():
@@ -895,6 +901,8 @@ def main():
895 classification_parser.add_argument('--table', action = 'store_true') 901 classification_parser.add_argument('--table', action = 'store_true')
896 classification_parser.add_argument('--table-format', dest='table_format', type=str, default='fancy_grid') 902 classification_parser.add_argument('--table-format', dest='table_format', type=str, default='fancy_grid')
897 classification_parser.set_defaults(cmd = partial(classification, classification_name=classification_name)) 903 classification_parser.set_defaults(cmd = partial(classification, classification_name=classification_name))
904 pull_forward_parser = subparsers.add_parser('pull-forward')
905 pull_forward_parser.set_defaults(cmd = pull_forward)
898 parser.set_default_subparser('time_worked') 906 parser.set_default_subparser('time_worked')
899 args = parser.parse_args() 907 args = parser.parse_args()
900 908