This repository has no description
17 kB
488 lines
1{
2 pkgs,
3 lib,
4 self,
5 ...
6}:
7let
8
9 term = "/run/current-system/sw/bin/kitty";
10
11 # Unified script: switch to workspace and move it to current output
12 gotoWorkspace = pkgs.writeShellApplication {
13 name = "goto-workspace";
14 runtimeInputs = [
15 pkgs.sway
16 pkgs.jq
17 ];
18 text = ''
19 WS="$1"
20 CURRENT_OUTPUT=$(swaymsg -r -t get_outputs | jq -r '.[] | select(.focused == true).name')
21 swaymsg workspace "$WS"
22 swaymsg move workspace to output "$CURRENT_OUTPUT"
23 '';
24 };
25
26in
27{
28 # Make goto-workspace available system-wide for workspace switching
29 environment.systemPackages = [ gotoWorkspace ];
30
31 # Firefox as a persistent systemd user service — starts after workspace-manager
32 # so the sway rule to hide the anchor window is already registered
33 systemd.user.services.firefox = {
34 description = "Firefox Web Browser";
35 partOf = [ "sway-session.target" ];
36 wantedBy = [ "sway-session.target" ];
37 after = [
38 "sway-session.target"
39 "workspace-manager.service"
40 ];
41 # Never restart on `nixos-rebuild switch` — that would kill all open
42 # browser windows. A new Firefox build is only picked up at the next
43 # sway session start.
44 restartIfChanged = false;
45 stopIfChanged = false;
46 serviceConfig = {
47 Type = "simple";
48 ExecStart = lib.getExe self.packages.${pkgs.system}.firefox;
49 Restart = "always";
50 RestartSec = 2;
51 };
52 };
53 imports = [
54 ../lib/wayland.nix
55 ./noctalia.nix
56 ./wallpaper.nix
57 ];
58 programs.sway = {
59 enable = true;
60 wrapperFeatures.gtk = true;
61 # Do NOT hardcode SWAYSOCK here: sway >=1.11 ignores an inherited value and
62 # creates its own per-pid socket (sway-ipc.$uid.$pid.sock). Exporting a
63 # fixed path baked a dead socket into the firefox/workspace-manager user
64 # services, breaking all swaymsg calls (and thus tab restore). Let sway
65 # advertise the real socket; the import-environment below propagates it.
66 };
67
68 # Enable realtime scheduling for sway
69 security.pam.loginLimits = [
70 {
71 domain = "@users";
72 item = "rtprio";
73 type = "-";
74 value = 1;
75 }
76 ];
77
78 systemd.user.services.swayidle = {
79 description = "Idle manager for Wayland";
80 documentation = [ "man:swayidle(1)" ];
81 partOf = [ "sway-session.target" ];
82 wantedBy = [ "sway-session.target" ];
83 serviceConfig = {
84 Type = "simple";
85 ExecStart = ''
86 ${pkgs.swayidle}/bin/swayidle -w \
87 timeout 120 '${lib.getExe' pkgs.systemd "systemctl"} --user start lock.target' \
88 timeout 300 '${lib.getExe' pkgs.systemd "systemctl"} suspend-then-hibernate' \
89 before-sleep '${lib.getExe' pkgs.systemd "systemctl"} --user start lock.target'
90 '';
91 Restart = "on-failure";
92 RestartSec = 1;
93 };
94 };
95
96 # Keep new windows on the workspace of the process that spawned them instead
97 # of the focused one (sway only tracks this for its own `exec`, and only for
98 # 60s).
99 systemd.user.services.sway-spawn-workspace = {
100 description = "Place new windows on their spawning process's workspace";
101 partOf = [ "sway-session.target" ];
102 wantedBy = [ "sway-session.target" ];
103 serviceConfig = {
104 Type = "simple";
105 ExecStart = lib.getExe self.packages.${pkgs.system}.sway-spawn-workspace;
106 Restart = "on-failure";
107 RestartSec = 2;
108 };
109 };
110
111 # Inhibit idle when audio is playing (prevents suspend during calls/music)
112 systemd.user.services.sway-audio-idle-inhibit = {
113 description = "Inhibit idle when audio is playing";
114 partOf = [ "sway-session.target" ];
115 wantedBy = [ "sway-session.target" ];
116 serviceConfig = {
117 Type = "simple";
118 ExecStart = "${pkgs.sway-audio-idle-inhibit}/bin/sway-audio-idle-inhibit";
119 Restart = "on-failure";
120 RestartSec = 1;
121 };
122 };
123
124 systemd.user.services.sway-urgent-rumble = {
125 description = "Trigger rumble on urgent windows";
126 partOf = [ "sway-session.target" ];
127 wantedBy = [ "sway-session.target" ];
128 serviceConfig = {
129 Type = "simple";
130 ExecStart = lib.getExe (
131 pkgs.writeShellApplication {
132 name = "sway-urgent-rumble";
133 runtimeInputs = [
134 pkgs.sway
135 pkgs.jq
136 self.packages.${pkgs.system}.gpd-rumble
137 ];
138 text = ''
139 # Find first force-feedback capable device
140 FF_DEVICE=""
141 for ev in /sys/class/input/event*; do
142 if [ -f "$ev/device/capabilities/ff" ] && [ "$(cat "$ev/device/capabilities/ff")" != "0" ]; then
143 FF_DEVICE="/dev/input/''${ev##*/}"
144 break
145 fi
146 done
147
148 if [ -z "$FF_DEVICE" ]; then
149 echo "No force-feedback device found, exiting"
150 exit 0
151 fi
152
153 echo "Using force-feedback device: $FF_DEVICE"
154
155 swaymsg -t subscribe -m '["window"]' | \
156 jq --unbuffered -r 'select(.change == "urgent" and .container.urgent == true) | "urgent"' | \
157 while read -r _; do
158 gpd-rumble 200 100 "$FF_DEVICE" &
159 done
160 '';
161 }
162 );
163 Restart = "on-failure";
164 RestartSec = 1;
165 };
166 };
167
168 environment.etc."sway/config".text = ''
169 # Default config for sway
170 #
171 # Copy this to ~/.config/sway/config and edit it to your liking.
172 #
173 # Read `man 5 sway` for a complete reference.
174
175 ### Variables
176 #
177 # Logo key. Use Mod1 for Alt.
178 set $mod Mod4
179 # Home row direction keys, like vim
180 set $left h
181 set $down j
182 set $up k
183 set $right l
184 # Your preferred terminal emulator
185 set $term ${term}
186 # Your preferred application launcher
187 # Note: pass the final command to swaymsg so that the resulting window can be opened
188 # on the original workspace that the command was run on.
189 set $menu ${pkgs.dmenu}/bin/dmenu_path | menu | xargs swaymsg exec --
190
191 ### Output configuration
192 #
193 # Default wallpaper (more resolutions are available in @datadir@/backgrounds/sway/)
194 # output * bg @datadir@/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png fill
195 #
196 # Example configuration:
197 #
198 # output HDMI-A-1 resolution 1920x1080 position 1920,0
199 #
200 # You can get the names of your outputs by running: swaymsg -t get_outputs
201
202 # Per-machine output overrides (scale, resolution, position) live here.
203 include /etc/sway/config.d/*.conf
204
205 ### Idle configuration
206 #
207 # Example configuration:
208 #
209 # exec swayidle -w \
210 # timeout 300 'swaylock -f -c 000000' \
211 # timeout 600 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' \
212 # before-sleep 'swaylock -f -c 000000'
213 #
214 # This will lock your screen after 300 seconds of inactivity, then turn off
215 # your displays after another 300 seconds, and turn your screens back on when
216 # resumed. It will also lock your screen before your computer goes to sleep.
217
218 ### Input configuration
219 #
220 # Example configuration:
221 #
222 input "2321:21128:HTIX5288:00_0911:5288_Touchpad" {
223 tap enabled
224 }
225 input "2362:628:PIXA3854:00_093A:0274_Touchpad" {
226 tap enabled
227 }
228 #
229 # You can get the names of your inputs by running: swaymsg -t get_inputs
230 # Read `man 5 sway-input` for more information about this section.
231
232 ### Key bindings
233 #
234 # Basics:
235 #
236 # Start a terminal
237 bindsym $mod+Shift+Return exec $term
238
239 # Kill focused window
240 bindsym $mod+Shift+c kill
241
242 # Start your launcher
243 bindsym $mod+d exec $menu
244
245 # Drag floating windows by holding down $mod and left mouse button.
246 # Resize them with right mouse button + $mod.
247 # Despite the name, also works for non-floating windows.
248 # Change normal to inverse to use left mouse button for resizing and right
249 # mouse button for dragging.
250 floating_modifier $mod normal
251
252 # Reload the configuration file
253 bindsym $mod+Shift+r reload
254
255 # Exit sway (logs you out of your Wayland session)
256 bindsym $mod+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -B 'Yes, exit sway' 'swaymsg exit'
257 #
258 # Moving around:
259 #
260 # Move your focus around
261 bindsym $mod+Left focus left
262 bindsym $mod+Down focus down
263 bindsym $mod+Up focus up
264 bindsym $mod+Right focus right
265
266 # Move the focused window with the same, but add Shift
267 bindsym $mod+Shift+Left move left
268 bindsym $mod+Shift+Down move down
269 bindsym $mod+Shift+Up move up
270 bindsym $mod+Shift+Right move right
271 # Layout stuff:
272 #
273 # Split bindings removed to avoid accidental sub-containers.
274 # Use $mod+w to flatten into tabs, $mod+e to toggle split direction.
275 bindsym $mod+n split none
276
277 # Switch the current container between different layout styles
278 bindsym $mod+s layout stacking
279 bindsym $mod+w layout tabbed
280 bindsym $mod+e layout toggle split
281
282 # Make the current focus fullscreen
283 bindsym $mod+f fullscreen
284
285 # Toggle the current focus between tiling and floating mode
286 bindsym $mod+Shift+space floating toggle
287
288 # Swap focus between the tiling area and the floating area
289 bindsym $mod+a focus mode_toggle
290
291 # Workspace manager menu
292 bindsym $mod+space exec ${lib.getExe self.packages.${pkgs.system}.workspace-menu}
293
294 # Move focus to the parent container
295 bindsym $mod+Shift+a focus parent
296 #
297 # Scratchpad:
298 #
299 # Sway has a "scratchpad", which is a bag of holding for windows.
300 # You can send windows there and get them back later.
301
302 # Move the currently focused window to the scratchpad
303 bindsym $mod+Shift+minus move scratchpad
304
305 # Show the next scratchpad window or hide the focused scratchpad window.
306 # If there are multiple scratchpad windows, this command cycles through them.
307 bindsym $mod+minus scratchpad show
308 #
309 # Resizing containers:
310 #
311 mode "resize" {
312 # left will shrink the containers width
313 # right will grow the containers width
314 # up will shrink the containers height
315 # down will grow the containers height
316 bindsym $left resize shrink width 10px
317 bindsym $down resize grow height 10px
318 bindsym $up resize shrink height 10px
319 bindsym $right resize grow width 10px
320
321 # Ditto, with arrow keys
322 bindsym Left resize shrink width 10px
323 bindsym Down resize grow height 10px
324 bindsym Up resize shrink height 10px
325 bindsym Right resize grow width 10px
326
327 # Return to default mode
328 bindsym Return mode "default"
329 bindsym Escape mode "default"
330 }
331 bindsym $mod+r mode "resize"
332
333 default_border pixel 1
334 default_floating_border none
335 smart_borders on
336
337 bindsym $mod+q exec ${pkgs.writers.writeDash "goto_workspace" ''
338 set -efux
339 WS=$(swaymsg -r -t get_workspaces |
340 jq -r '.[].name' |
341 menu -p 'Workspace name: '
342 )
343 if [ -n "$WS" ]; then
344 ${lib.getExe gotoWorkspace} "$WS"
345 fi
346 ''}
347
348 bindsym $mod+Shift+q exec ${pkgs.writers.writeDash "moveto_workspace" ''
349 set -efux
350 WS=$(swaymsg -r -t get_workspaces |
351 jq -r '.[].name' |
352 menu -p 'Workspace name: '
353 )
354 if [ -n "$WS" ]; then
355 swaymsg move container to workspace "$WS"
356 fi
357 ''}
358
359 bindsym $mod+y exec /run/current-system/sw/bin/switch-theme toggle
360
361 bindsym $mod+Tab exec ${pkgs.sway-overfocus}/bin/sway-overfocus split-rw group-rw split-dw group-dw
362 bindsym $mod+Escape workspace back_and_forth
363
364 # Focus primary output
365 bindsym $mod+F1 exec ${
366 lib.getExe (
367 pkgs.writeShellApplication {
368 name = "focus-primary-output";
369 runtimeInputs = [
370 pkgs.sway
371 pkgs.jq
372 ];
373 text = ''
374 PRIMARY=$(swaymsg -r -t get_outputs | jq -r 'sort_by(.x, .y) | .[0].name')
375 swaymsg focus output "$PRIMARY"
376 '';
377 }
378 )
379 }
380
381 # Cycle through secondary outputs
382 bindsym $mod+F2 exec ${
383 lib.getExe (
384 pkgs.writeShellApplication {
385 name = "cycle-secondary-outputs";
386 runtimeInputs = [
387 pkgs.sway
388 pkgs.jq
389 pkgs.gnugrep
390 pkgs.coreutils
391 ];
392 text = ''
393 OUTPUTS=$(swaymsg -r -t get_outputs | jq -r 'sort_by(.x, .y)')
394 PRIMARY=$(echo "$OUTPUTS" | jq -r '.[0].name')
395 CURRENT=$(echo "$OUTPUTS" | jq -r '.[] | select(.focused == true).name')
396 SECONDARIES=$(echo "$OUTPUTS" | jq -r '.[1:] | .[].name')
397
398 if [ -z "$SECONDARIES" ]; then
399 exit 0
400 fi
401
402 if [ "$CURRENT" = "$PRIMARY" ]; then
403 # Focus first secondary
404 NEXT=$(echo "$SECONDARIES" | head -n1)
405 else
406 # Find next secondary in the list
407 NEXT=$(echo "$SECONDARIES" | grep -A1 "^$CURRENT$" | tail -n1)
408 # If we're at the end, wrap to first secondary
409 if [ "$NEXT" = "$CURRENT" ]; then
410 NEXT=$(echo "$SECONDARIES" | head -n1)
411 fi
412 fi
413
414 swaymsg focus output "$NEXT"
415 '';
416 }
417 )
418 }
419
420 # screenlock (random live video/image from ~/wallpaper via swaylock-plugin)
421 bindsym $mod+F11 exec ${lib.getExe' pkgs.systemd "systemctl"} --user start lock.target
422 bindsym $mod+l exec ${lib.getExe' pkgs.systemd "systemctl"} --user start lock.target
423
424 # kill window (xkill-like)
425 mode "kill" {
426 bindsym --whole-window button1 kill, mode "default"
427 bindsym --whole-window button2 mode "default"
428 bindsym --whole-window button3 mode "default"
429 bindsym Escape mode "default"
430 }
431 bindsym $mod+x mode "kill"
432
433 # media buttons
434 bindsym XF86AudioMute exec ${pkgs.pulseaudio.out}/bin/pactl -- set-sink-mute @DEFAULT_SINK@ toggle
435 bindsym XF86AudioRaiseVolume exec ${pkgs.pulseaudio.out}/bin/pactl -- set-sink-volume @DEFAULT_SINK@ +4%
436 bindsym XF86AudioLowerVolume exec ${pkgs.pulseaudio.out}/bin/pactl -- set-sink-volume @DEFAULT_SINK@ -4%
437
438 # brightness keys
439 bindsym --locked XF86MonBrightnessDown exec brightnessctl set 5%-
440 bindsym --locked XF86MonBrightnessUp exec brightnessctl set 5%+
441
442 # background programs
443 exec ${pkgs.copyq}/bin/copyq --start-server
444 exec ydotoold
445 exec_always pkill kanshi; exec ${pkgs.kanshi}/bin/kanshi
446
447
448 # theme and env specific stuff
449 exec_always ${pkgs.writers.writeDash "dbus-sway-environment" ''
450 set -efux
451 # XDG_SESSION_TYPE=wayland must be exported here: with DISPLAY set but
452 # XDG_SESSION_TYPE unset, Firefox (and other apps) started from the systemd
453 # user manager pick the X11/XWayland screencast path, which captures a black
454 # screen under sway. Setting it explicitly makes them use the PipeWire portal.
455 dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP=sway XDG_SESSION_TYPE=wayland
456 systemctl --user set-environment XDG_SESSION_TYPE=wayland
457 systemctl --user import-environment DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
458 systemctl --user start --no-block sway-session.target
459 systemctl --user stop xdg-desktop-portal xdg-desktop-portal-wlr
460 systemctl --user start xdg-desktop-portal xdg-desktop-portal-wlr
461 ''}
462
463 exec_always ${pkgs.writers.writeDash "gsettings" ''
464 set -efux
465 export XDG_DATA_DIRS=${pkgs.gsettings-desktop-schemas}:$XDG_DATA_DIRS
466 gnome_schema=org.gnome.desktop.interface
467 gsettings set $gnome_schema gtk-theme 'adw-gtk3-dark'
468 ''}
469
470 # keyboard
471 input type:keyboard {
472 xkb_layout us
473 xkb_variant altgr-intl
474 xkb_options caps:hyper
475 }
476
477 # Hide the workspace-manager Firefox anchor window. Registered statically
478 # here (not just by the daemon at runtime) so the rule exists before the
479 # firefox.service starts, otherwise the anchor flashes onto the current
480 # workspace and gets captured as a phantom tab.
481 for_window [title="^workspace-anchor"] move scratchpad
482
483 # flameshot
484 for_window [app_id="flameshot"] border pixel 0, floating enable, fullscreen disable, move absolute position 0 0
485 exec ${pkgs.flameshot}/bin/flameshot
486 '';
487
488}