Kieran's opinionated (and probably slightly dumb) nix config
1{
2 lib,
3 pkgs,
4 config,
5 ...
6}:
7let
8 commonDeps = with pkgs; [
9 coreutils
10 gnugrep
11 systemd
12 ];
13 # Function to simplify making waybar outputs
14 mkScript =
15 {
16 name ? "script",
17 deps ? [ ],
18 script ? "",
19 }:
20 lib.getExe (
21 pkgs.writeShellApplication {
22 inherit name;
23 text = script;
24 runtimeInputs = commonDeps ++ deps;
25 }
26 );
27 # Specialized for JSON outputs
28 mkScriptJson =
29 {
30 name ? "script",
31 deps ? [ ],
32 pre ? "",
33 text ? "",
34 tooltip ? "",
35 alt ? "",
36 class ? "",
37 percentage ? "",
38 }:
39 mkScript {
40 inherit name;
41 deps = [ pkgs.jq ] ++ deps;
42 script = ''
43 ${pre}
44 jq -cn \
45 --arg text "${text}" \
46 --arg tooltip "${tooltip}" \
47 --arg alt "${alt}" \
48 --arg class "${class}" \
49 --arg percentage "${percentage}" \
50 '{text:$text,tooltip:$tooltip,alt:$alt,class:$class,percentage:$percentage}'
51 '';
52 };
53in
54{
55 config = lib.mkIf config.atelier.wm.hyprland.enable {
56 # Let it try to start a few more times
57 systemd.user.services.waybar = {
58 Unit.StartLimitBurst = 30;
59 };
60 programs.waybar = {
61 enable = true;
62 package = pkgs.waybar.overrideAttrs (oa: {
63 mesonFlags = (oa.mesonFlags or [ ]) ++ [ "-Dexperimental=true" ];
64 });
65 systemd.enable = true;
66 settings = {
67 primary = {
68 exclusive = false;
69 passthrough = false;
70 height = 46;
71 margin = "6";
72 position = "top";
73 modules-left = [
74 "custom/os"
75 "hyprland/workspaces"
76 "hyprland/submap"
77 ];
78
79 modules-center = [
80 "cpu"
81 "memory"
82 "disk"
83 "clock"
84 "pulseaudio"
85 "battery"
86 "idle_inhibitor"
87 ];
88
89 modules-right = [
90 "network"
91 "bluetooth"
92 "tray"
93 "privacy"
94 "custom/webcam"
95 "custom/hostname"
96 ];
97
98 clock = {
99 interval = 1;
100 format = "{:%d/%m %H:%M:%S}";
101 format-alt = "{:%Y-%m-%d %H:%M:%S %z}";
102 on-click-left = "mode";
103 tooltip-format = ''
104 <big>{:%Y %B}</big>
105 <tt><small>{calendar}</small></tt>'';
106 };
107
108 cpu = {
109 format = " {usage}%";
110 };
111
112 memory = {
113 format = " {}%";
114 interval = 5;
115 };
116
117 disk = {
118 interval = 5;
119 unit = "GB";
120 format = " {percentage_used}%";
121 };
122
123 pulseaudio = {
124 format = "{icon} {volume}%";
125 format-muted = " 0%";
126 format-icons = {
127 headphone = " ";
128 headset = " ";
129 portable = " ";
130 default = [
131 " "
132 " "
133 " "
134 ];
135 };
136 on-click = lib.getExe pkgs.pavucontrol;
137 };
138
139 idle_inhibitor = {
140 format = "{icon}";
141 format-icons = {
142 activated = " ";
143 deactivated = " ";
144 };
145 tooltip-format-activated = "Caffinated! device will not sleep.";
146 tooltip-format-deactivated = "no caffeine :( device will sleep when not in use.";
147 };
148
149 battery = {
150 interval = 5;
151 bat = "BAT1";
152 # full-at = 94;
153 format = "{icon} {capacity}%";
154 format-icons = [
155 ""
156 ""
157 ""
158 ""
159 ""
160 ""
161 ""
162 ""
163 ""
164 ""
165 ];
166 states = {
167 battery-10 = 10;
168 battery-20 = 20;
169 battery-30 = 30;
170 battery-40 = 40;
171 battery-50 = 50;
172 battery-60 = 60;
173 battery-70 = 70;
174 battery-80 = 80;
175 battery-90 = 90;
176 battery-100 = 100;
177 };
178 # <https://github.com/Alexays/Waybar/issues/1938>
179 # the wiki lies about this, does not match
180 # /sys/class/power_supply/BAT0/status
181 format-plugged = " AC";
182 format-charging-battery-10 = " {capacity}%";
183 format-charging-battery-20 = " {capacity}%";
184 format-charging-battery-30 = " {capacity}%";
185 format-charging-battery-40 = " {capacity}%";
186 format-charging-battery-50 = " {capacity}%";
187 format-charging-battery-60 = " {capacity}%";
188 format-charging-battery-70 = " {capacity}%";
189 format-charging-battery-80 = " {capacity}%";
190 format-charging-battery-90 = " {capacity}%";
191 format-charging-battery-100 = " {capacity}%";
192 };
193
194 "hyprland/workspaces" = {
195 format = "{icon} {windows}";
196 window-rewrite-default = " ";
197 window-rewrite-seperator = "";
198 window-rewrite = {
199 "title<.*github.*>" = " ";
200 "title<.*youtube.*>" = " ";
201 "title<*Gmail*>" = " ";
202 "class<firefox>" = " ";
203 "obs" = " ";
204 "alacritty" = " ";
205 "class<com.mitchellh.ghostty>" = " ";
206 "foot" = " ";
207 "code" = " ";
208 "slack" = " ";
209 "spotify" = " ";
210 "class<org.gnome.Nautilus>" = " ";
211 "class<kicad>" = " ";
212 "class<dev.zed.Zed>" = " ";
213 "class<chromium-browser>" = " ";
214 "class<vesktop>" = " ";
215 };
216 };
217
218 network = {
219 interval = 3;
220 format-wifi = "{icon} {essid}";
221 format-icons = [
222 ""
223 ""
224 ""
225 ""
226 ];
227 format-ethernet = " Connected";
228 format-disconnected = " ";
229 tooltip-format = ''
230 {ifname}
231 {ipaddr}/{cidr}
232 Up: {bandwidthUpBits}
233 Down: {bandwidthDownBits}'';
234 on-click = mkScript {
235 deps = [
236 pkgs.wpa_supplicant
237 pkgs.notify-desktop
238 ];
239 script = ''wpa_cli reconnect; notify-desktop "reconnecting to wifi" -t 1200'';
240 };
241 };
242
243 bluetooth = {
244 format-on = "";
245 format-off = "";
246 format-disabled = "";
247 format-connected = " {num_connections}";
248 format-connected-battery = " {device_alias} ({device_battery_percentage}%) ({num_connections})";
249 on-click = "overskride";
250 };
251
252 "custom/os" = {
253 interval = 1;
254 return-type = "json";
255 exec = mkScriptJson {
256 text = " ";
257 tooltip = ''$(grep PRETTY_NAME /etc/os-release | cut -d '"' -f2)'';
258 };
259 };
260
261 "custom/hostname" = {
262 exec = mkScript { script = ''echo "$USER@$HOSTNAME"''; };
263 on-click = mkScript { script = "systemctl --user restart waybar"; };
264 };
265
266 privacy = {
267 "icon-spacing" = 0;
268 "icon-size" = 18;
269 "transition-duration" = 250;
270 modules = [
271 {
272 type = "screenshare";
273 tooltip = true;
274 "tooltip-icon-size" = 24;
275 }
276 {
277 type = "audio-out";
278 tooltip = true;
279 "tooltip-icon-size" = 24;
280 }
281 {
282 type = "audio-in";
283 tooltip = true;
284 "tooltip-icon-size" = 24;
285 }
286 ];
287 };
288
289 "custom/webcam" = {
290 return-type = "json";
291 interval = 2;
292 exec = mkScript {
293 deps = [
294 pkgs.jq
295 pkgs.psmisc
296 ];
297 script = ''
298 # get programs using the video0 endpoint
299 PIDS=$(fuser /dev/video0 2>/dev/null || echo "")
300 if [ -n "$PIDS" ]; then
301 # Using pgrep instead of grepping ps output
302 for pid in $PIDS; do
303 process_info=$(ps -p "$pid" -o pid,cmd --no-headers)
304 if [ -n "$process_info" ]; then
305 echo "$process_info" | awk '{command=$2; for(i=3;i<=NF;i++) command=command" "$i; print "{\"tooltip\": \""command"\"}"}'
306 fi
307 done | grep -v "grep" |\
308 jq -s 'if length > 0 then {text: " ", tooltip: (map(.tooltip) | join("\r"))} else {text: "", tooltip: ""} end'
309 else
310 echo '{"text": "", "tooltip": ""}'
311 fi | jq --unbuffered --compact-output
312 '';
313 };
314 };
315 };
316 };
317
318 # Cheatsheet:
319 # x -> all sides
320 # x y -> vertical, horizontal
321 # x y z -> top, horizontal, bottom
322 # w x y z -> top, right, bottom, left
323 style =
324 # css
325 ''
326 * {
327 font-family: Fira Sans, FiraCode Nerd Font;
328 font-size: 12pt;
329 padding: 0;
330 margin: 0 0.4em;
331 }
332
333 window#waybar {
334 padding: 0;
335 border-radius: 0.5em;
336 background-color: shade(@surface0, 0.7);
337 color: @surface2
338 }
339 .modules-left {
340 margin-left: -0.65em;
341 }
342 .modules-right {
343 margin-right: -0.65em;
344 }
345
346 #workspaces button {
347 background-color: @surface0;
348 color: @surface2;
349 padding-left: 0.2em;
350 padding-right: 0.2em;
351 margin-left: 0.25em;
352 margin-right: 0.25em;
353 margin-top: 0.4em;
354 margin-bottom: 0.4em;
355 }
356 #workspaces button.hidden {
357 background-color: @surface0;
358 color: @surface2;
359 }
360 #workspaces button.focused,
361 #workspaces button.active {
362 background-color: shade(@blue, 0.7);
363 color: @green;
364 }
365
366 #workspaces button:hover {
367 background-color: @surface3;
368 color: @surface1;
369 }
370
371 #privacy-item {
372 margin-left: 0.1em;
373 margin-right: 0.1em;
374 }
375
376 #clock {
377 padding-right: 1em;
378 padding-left: 1em;
379 border-radius: 0.5em;
380 }
381
382 #custom-os {
383 background-color: @surface3;
384 color: @blue;
385 padding-right: 1em;
386 padding-left: 1em;
387 margin-right: 0;
388 border-radius: 0.5em;
389 }
390 #custom-hostname {
391 background-color: @surface3;
392 color: @blue;
393 padding-right: 1em;
394 padding-left: 1em;
395 margin-left: 0;
396 border-radius: 0.5em;
397 }
398 #custom-gpu, #cpu, #memory {
399 margin-left: 0.05em;
400 margin-right: 0.55em;
401 }
402 '';
403 };
404 };
405}