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