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