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