{ config, lib, nixosConfig, pkgs, ... }: let common = import ./common.nix; solarized = common.colorschemes.solarized; # Source: “境目” by 3211 on Pixiv: https://www.pixiv.net/en/artworks/39266182 wallpaperUnfree = pkgs.fetchurl { name = "wallpaper-unfree"; url = "https://i.pximg.net/img-original/img/2013/10/22/04/25/37/39266182_p0.jpg"; sha256 = "053gc9jd4cbkkwgcirrhpzbn933dfh83l30p1sz55m5d8zx1lk65"; curlOpts = "--referer https://pixiv.net"; meta.license = lib.licenses.unfree; }; wallpaperFree = pkgs.fetchurl { url = "https://raw.githubusercontent.com/swaywm/sway/3b2bc894a5ebbcbbd6707d45a25d171779c2e874/assets/Sway_Wallpaper_Blue_1920x1080.png"; sha256 = "1rkqd0h7w64plibn7k3krk5vdc3pnv3fc7m2xc2mxnwrbsgngwsz"; meta.license = lib.licenses.cc0; }; wallpaper = if nixosConfig.sbruder.unfree.allowAssets then wallpaperUnfree else wallpaperFree; cfg = config.wayland.windowManager.sway.config; makoInhibitStateFile = "${config.xdg.dataHome}/mako/inhibit-state"; in lib.mkIf nixosConfig.sbruder.gui.enable { wayland.windowManager.sway = { enable = true; systemdIntegration = true; # TODO: remove patch once sway > 1.5.1 is released and in nixpkgs package = pkgs.sway.override { sway-unwrapped = pkgs.sway-unwrapped.overrideAttrs (o: o // { patches = lib.singleton (pkgs.fetchpatch { url = "https://github.com/swaywm/sway/commit/33b64667ae0bfe9af752cb8c850b4f52b34660d2.patch"; sha256 = "05s0xhd4wn98insn0wynr359nq938dmc7aa9rmyn7ira78fw40w8"; }); }); }; config = { modifier = "Mod4"; terminal = "alacritty"; menu = "${pkgs.dmenu-wayland}/bin/dmenu-wl_path | ${pkgs.dmenu-wayland}/bin/dmenu-wl -nb '#002b36' -nf '#839496' -sb '#859900' -sf '#073642' | ${pkgs.findutils}/bin/xargs swaymsg exec --"; output."*".bg = "${wallpaper} fill"; input."type:keyboard" = { xkb_layout = "de"; xkb_options = "compose:caps"; xkb_numlock = "enable"; }; keybindings = { # Basics "${cfg.modifier}+Return" = "exec ${cfg.terminal}"; "${cfg.modifier}+Shift+c" = "kill"; "${cfg.modifier}+r" = "exec ${cfg.menu}"; "${cfg.modifier}+Control+r" = "reload"; "${cfg.modifier}+Shift+q" = "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'"; # Focus "${cfg.modifier}+${cfg.left}" = "focus left"; "${cfg.modifier}+${cfg.down}" = "focus down"; "${cfg.modifier}+${cfg.up}" = "focus up"; "${cfg.modifier}+${cfg.right}" = "focus right"; "${cfg.modifier}+Left" = "focus left"; "${cfg.modifier}+Down" = "focus down"; "${cfg.modifier}+Up" = "focus up"; "${cfg.modifier}+Right" = "focus right"; # Moving "${cfg.modifier}+Shift+${cfg.left}" = "move left"; "${cfg.modifier}+Shift+${cfg.down}" = "move down"; "${cfg.modifier}+Shift+${cfg.up}" = "move up"; "${cfg.modifier}+Shift+${cfg.right}" = "move right"; "${cfg.modifier}+Shift+Left" = "move left"; "${cfg.modifier}+Shift+Down" = "move down"; "${cfg.modifier}+Shift+Up" = "move up"; "${cfg.modifier}+Shift+Right" = "move right"; # Workspaces "${cfg.modifier}+1" = "workspace number 1"; "${cfg.modifier}+2" = "workspace number 2"; "${cfg.modifier}+3" = "workspace number 3"; "${cfg.modifier}+4" = "workspace number 4"; "${cfg.modifier}+5" = "workspace number 5"; "${cfg.modifier}+6" = "workspace number 6"; "${cfg.modifier}+7" = "workspace number 7"; "${cfg.modifier}+8" = "workspace number 8"; "${cfg.modifier}+9" = "workspace number 9"; "${cfg.modifier}+0" = "workspace number 10"; "${cfg.modifier}+Shift+1" = "move container to workspace number 1"; "${cfg.modifier}+Shift+2" = "move container to workspace number 2"; "${cfg.modifier}+Shift+3" = "move container to workspace number 3"; "${cfg.modifier}+Shift+4" = "move container to workspace number 4"; "${cfg.modifier}+Shift+5" = "move container to workspace number 5"; "${cfg.modifier}+Shift+6" = "move container to workspace number 6"; "${cfg.modifier}+Shift+7" = "move container to workspace number 7"; "${cfg.modifier}+Shift+8" = "move container to workspace number 8"; "${cfg.modifier}+Shift+9" = "move container to workspace number 9"; "${cfg.modifier}+Shift+0" = "move container to workspace number 10"; # Moving workspaces between outputs "${cfg.modifier}+Control+${cfg.left}" = "move workspace to output left"; "${cfg.modifier}+Control+${cfg.down}" = "move workspace to output down"; "${cfg.modifier}+Control+${cfg.up}" = "move workspace to output up"; "${cfg.modifier}+Control+${cfg.right}" = "move workspace to output right"; "${cfg.modifier}+Control+Left" = "move workspace to output left"; "${cfg.modifier}+Control+Down" = "move workspace to output down"; "${cfg.modifier}+Control+Up" = "move workspace to output up"; "${cfg.modifier}+Control+Right" = "move workspace to output right"; # Splits "${cfg.modifier}+b" = "splith"; "${cfg.modifier}+v" = "splitv"; # Layouts "${cfg.modifier}+s" = "layout stacking"; "${cfg.modifier}+t" = "layout tabbed"; "${cfg.modifier}+e" = "layout toggle split"; "${cfg.modifier}+f" = "fullscreen toggle"; "${cfg.modifier}+a" = "focus parent"; "${cfg.modifier}+Control+space" = "floating toggle"; "${cfg.modifier}+space" = "focus mode_toggle"; # Scratchpad "${cfg.modifier}+Shift+minus" = "move scratchpad"; "${cfg.modifier}+minus" = "scratchpad show"; # Resize mode "${cfg.modifier}+d" = "mode resize"; # Multimedia Keys "XF86AudioMute" = "exec ${pkgs.pulseaudio}/bin/pactl set-sink-mute @DEFAULT_SINK@ toggle"; "XF86AudioMicMute" = "exec ${pkgs.pulseaudio}/bin/pactl set-source-mute @DEFAULT_SOURCE@ toggle"; "--locked XF86MonBrightnessDown" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-"; "--locked XF86MonBrightnessUp" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set +5%"; "XF86AudioRaiseVolume" = "exec ${pkgs.pulseaudio}/bin/pactl set-sink-volume @DEFAULT_SINK@ +5%"; "XF86AudioLowerVolume" = "exec ${pkgs.pulseaudio}/bin/pactl set-sink-volume @DEFAULT_SINK@ -5%"; "XF86AudioPrev" = "exec ${pkgs.mpc_cli}/bin/mpc -q next"; "XF86AudioNext" = "exec ${pkgs.mpc_cli}/bin/mpc -q prev"; "XF86AudioPlay" = "exec ${pkgs.mpc_cli}/bin/mpc -q toggle"; # Mumble PTT "--no-repeat Shift_R" = "exec ${pkgs.dbus}/bin/dbus-send --session --type=method_call --dest=net.sourceforge.mumble.mumble / net.sourceforge.mumble.Mumble.startTalking"; "--no-repeat --release Shift_R" = "exec ${pkgs.dbus}/bin/dbus-send --session --type=method_call --dest=net.sourceforge.mumble.mumble / net.sourceforge.mumble.Mumble.stopTalking"; # reset "Shift_R+Shift" = "exec ${pkgs.dbus}/bin/dbus-send --session --type=method_call --dest=net.sourceforge.mumble.mumble / net.sourceforge.mumble.Mumble.stopTalking"; # Locking and DPMS "${cfg.modifier}+y" = "exec ${pkgs.swaylock}/bin/swaylock -f -i ${wallpaper}"; "--locked ${cfg.modifier}+q" = ''exec 'test $(swaymsg -t get_outputs | ${pkgs.jq}/bin/jq "[.[].dpms] | any") = "true" && swaymsg "output * dpms off" || swaymsg "output * dpms on"''; }; bars = [ ]; # managed as systemd user unit assigns = { "2" = [ { app_id = "firefox"; } #{ class="Chromium"; } ]; "3" = [ { app_id = "net.sourceforge.mumble."; } { class = "Claws-mail"; } ]; "4" = [ { app_id = "anki"; } { app_id = "libreoffice-startcenter"; } { app_id = "net.sourceforge.gscan2pdf"; } { app_id = "org.pwmt.zathura"; } { app_id = "xournalpp"; } ]; "5" = [ { app_id = "audacious"; } { app_id = "pavucontrol"; } ]; "8" = [ { app_id = "darktable"; } { app_id = "org.inkscape.Inkscape"; } { class = "Blender"; } { class = "Gimp"; } { class = "krita"; } ]; }; window.border = 1; floating = { titlebar = true; border = 1; }; colors = { focused = rec { border = "#93a1a1"; background = "#073642"; text = "#93a1a1"; indicator = "#2aa198"; childBorder = background; }; focusedInactive = rec { border = "#839496"; background = "#002b36"; text = "#839496"; indicator = "#2aa198"; childBorder = background; }; unfocused = rec { border = "#839496"; background = "#002b36"; text = "#839496"; indicator = "#2aa198"; childBorder = background; }; urgent = rec { border = "#073642"; background = "#dc322f"; text = "#073642"; indicator = "#2aa198"; childBorder = background; }; }; fonts = [ "monospace 10" ]; }; extraConfig = '' # Cursor seat seat0 xcursor_theme Adwaita ''; }; home.sessionVariables = { CLUTTER_BACKEND = "wayland"; GDK_BACKEND = "wayland"; GDK_DPI_SCALE = 1; MOZ_ENABLE_WAYLAND = 1; QT_QPA_PLATFORM = "wayland-egl"; QT_WAYLAND_DISABLE_WINDOWDECORATION = 1; SDL_VIDEODRIVER = "wayland"; WLR_NO_HARDWARE_CURSORS = 1; _JAVA_AWT_WM_NONREPARENTING = 1; _JAVA_OPTIONS = "-Dawt.useSystemAAFontSettings=on"; }; systemd.user.services.swayidle = { Unit.PartOf = [ "sway-session.target" ]; Install.WantedBy = [ "sway-session.target" ]; Service = { ExecStart = '' ${pkgs.swayidle}/bin/swayidle -w \ timeout 300 "${pkgs.swaylock}/bin/swaylock -f -i ${wallpaper}" \ timeout 300 'swaymsg "output * dpms off"' \ resume 'swaymsg "output * dpms on"' \ before-sleep "${pkgs.swaylock}/bin/swaylock -f -i ${wallpaper}" ''; Restart = "on-failure"; }; }; # Start on tty1 programs.zsh.initExtra = '' if [[ -z $WAYLAND_DISPLAY ]] && [[ $(tty) = /dev/tty1 ]]; then export XDG_SESSION_TYPE="wayland" # otherwise set to tty unset __HM_SESS_VARS_SOURCED # otherwise sessionVariables are not updated exec systemd-cat -t sway sway fi ''; home.packages = with pkgs; [ alacritty # terminal brightnessctl # control screen brightness sway-contrib.grimshot # screenshots wdisplays # graphical output manager ]; xdg.configFile."swaynag/config".text = let # adding it to the header doesn’t work since the defaults overwrite it commonConfig = '' background=fdf6e3 border-bottom=eee8d5 border=eee8d5 button-background=eee8d5 ''; in '' font=Monospace 12 [warning] text=b58900 ${commonConfig} [error] text=dc322f ${commonConfig} ''; # home-manager’s waybar module performs additional checks that are overly strict xdg.configFile."waybar/config".text = let watchUserUnitState = unit: started: stopped: pkgs.writeShellScript "watch-user-unit-${unit}-state" '' ${pkgs.systemd}/bin/journalctl --user -u ${unit} -t systemd -o cat -f \ | ${pkgs.gnugrep}/bin/grep --line-buffered -Eo '^(Started|Stopped)' \ | ${pkgs.jq}/bin/jq --unbuffered -Rc 'if . == "Started" then ${builtins.toJSON started} else ${builtins.toJSON stopped} end' ''; toggleUserUnitState = unit: pkgs.writeShellScript "toggle-user-unit-${unit}-state" '' if ${pkgs.systemd}/bin/systemctl --user show ${unit} | ${pkgs.gnugrep}/bin/grep -q ActiveState=active; then ${pkgs.systemd}/bin/systemctl --user stop ${unit} else ${pkgs.systemd}/bin/systemctl --user start ${unit} fi ''; # nerd fonts are abusing arabic which breaks latin text # context: https://github.com/Alexays/Waybar/issues/628 lrm = "‎"; # for fine-grained control over spacing thinsp = " "; in lib.generators.toJSON { } { layer = "top"; position = "top"; height = 24; modules-center = [ ]; modules-left = [ "sway/workspaces" "sway/mode" ]; modules-right = [ "tray" "custom/redshift" "idle_inhibitor" "custom/notification_inhibitor" "backlight" "mpd" "pulseaudio" "network" "custom/vpn" "network#speed" "memory" "cpu" "temperature" "battery" "clock" ]; "sway/workspaces" = { disable-scroll = true; }; "sway/mode" = { format = "{}"; }; tray = { spacing = 5; }; "custom/redshift" = { exec = watchUserUnitState "redshift" { class = "active"; } { class = "inactive"; }; on-click = toggleUserUnitState "redshift"; return-type = "json"; format = ""; tooltip = false; }; idle_inhibitor = { format = "{icon}"; format-icons = { activated = " "; deactivated = " "; }; }; "custom/notification_inhibitor" = { exec = pkgs.writeShellScript "notification-inhibitor-watch-state" '' set -euo pipefail (echo; ${pkgs.inotify-tools}/bin/inotifywait -m -e close_write ${lib.escapeShellArg makoInhibitStateFile} 2>/dev/null) \ | (while read line; do if [ "$(cat ${lib.escapeShellArg makoInhibitStateFile})" = "1" ]; then echo '{"alt": "active", "class": "active"}' else echo '{"alt": "inactive", "class": "inactive"}' fi done) ''; return-type = "json"; format = "{icon}"; format-icons = { active = "${lrm}ﮖ "; inactive = "${lrm}ﮗ "; }; tooltip = false; on-click = pkgs.writeShellScript "toggle-notification-inhibit" '' set -e current_state="$(cat ${lib.escapeShellArg makoInhibitStateFile})" if [ "$current_state" = "0" ]; then ${pkgs.mako}/bin/makoctl set invisible=true echo 1 > ${lib.escapeShellArg makoInhibitStateFile} else ${pkgs.mako}/bin/makoctl set invisible=false echo 0 > ${lib.escapeShellArg makoInhibitStateFile} fi ''; }; backlight = { format = "{percent}% {icon}"; format-icons = [ " " " " " " " " " " " " " " ]; on-scroll-up = "brightnessctl set +5%"; on-scroll-down = "brightnessctl set 5%-"; }; mpd = { server = "${config.services.mpd.dataDir}/socket"; format = "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} – {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) "; format-disconnected = "Disconnected "; format-stopped = "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped "; unknown-tag = "N/A"; interval = 2; tooltip-format = "MPD (connected)"; tooltip-format-disconnected = "MPD (disconnected)"; on-scroll-up = "${pkgs.mpc_cli}/bin/mpc -q volume +2"; on-scroll-down = "${pkgs.mpc_cli}/bin/mpc -q volume -2"; consume-icons = { on = " "; }; random-icons = { off = "劣 "; on = "列 "; }; repeat-icons = { on = "凌 "; }; single-icons = { on = "綾 "; }; state-icons = { paused = ""; playing = "契"; }; }; pulseaudio = { format = "{volume}% {icon} {format_source}"; format-bluetooth = "{volume}% {icon} {format_source}"; format-bluetooth-muted = "${lrm}ﱝ${lrm} {icon} {format_source}"; format-muted = "${lrm}ﱝ${lrm} {format_source}"; format-source = "{volume}% ${thinsp}"; format-source-muted = "${thinsp}"; format-icons = { car = " "; default = [ "奄" "奔" "墳" ]; hands-free = " "; headphone = " "; headset = " "; phone = " "; portable = " "; }; on-click-right = "pavucontrol"; }; network = { format-wifi = "{essid} ({signalStrength}%) 直 "; format-ethernet = "{ifname}: {ipaddr}/{cidr}  "; format-linked = "{ifname} (No IP)  "; format-disconnected = "Disconnected ⚠ "; format-alt = "{ifname}: {ipaddr}/{cidr}"; tooltip = false; on-click-right = "alacritty -e nmtui"; }; "custom/vpn" = { interval = 10; exec = pkgs.writeShellScript "vpn-state" '' ip -j link \ | jq --unbuffered --compact-output ' [[.[].ifname | select(. | startswith("mullvad"))][] | split("-")[1] + " ${thinsp}"] as $conns | { text: ($conns[0] // ""), class: (if $conns | length > 0 then "connected" else "disconnected" end) }' ''; return-type = "json"; format = "{}"; tooltip = false; }; "network#speed" = { interval = 2; format = "{bandwidthDownBits:>8}  {bandwidthUpBits:>8} 祝"; format-alt = "{bandwidthDownOctets:>8}  {bandwidthUpOctets:>8} 祝"; format-disconnected = " 0b/s 祝 0b/s"; tooltip = false; }; memory = { interval = 2; format = "{:2}%  "; }; cpu = { interval = 2; format = "{usage:2}% ﬙ "; tooltip = false; }; temperature = { critical-threshold = 80; format = "{temperatureC}°C {icon}"; format-icons = [ "" "" "" "" "" ]; }; battery = { interval = 5; format = "{capacity}% {icon}"; format-charging = "{capacity}% "; format-plugged = "{capacity}% ﮣ"; format-alt = "{time} {icon}"; format-icons = [ "" "" "" "" "" "" "" "" "" "" "" ]; states = { critical = 15; good = 95; warning = 30; }; }; clock = { format = "{:%H:%M %Z}"; format-alt = "{:%Y-%m-%d (%a)}"; tooltip-format = "{:%Y %B}\n{calendar}"; }; }; xdg.configFile."waybar/style.css".source = pkgs.substituteAll ({ src = ../files/waybar/style.css; } // solarized); systemd.user.services.waybar = { Unit = { Description = "Highly customizable Wayland bar for Sway and Wlroots based compositors."; Documentation = "https://github.com/Alexays/Waybar/wiki/"; PartOf = [ "sway-session.target" ]; }; Install.WantedBy = [ "sway-session.target" ]; Service = { # ensure sway is already started, otherwise workspaces will not work ExecStartPre = "${config.wayland.windowManager.sway.package}/bin/swaymsg"; # FIXME: Remove override with 21.05 (https://github.com/NixOS/nixpkgs/pull/98490) ExecStart = "${pkgs.waybar.override { pulseSupport = true; }}/bin/waybar"; ExecReload = "${pkgs.utillinux}/bin/kill -SIGUSR2 $MAINPID"; Restart = "on-failure"; RestartSec = "1s"; }; }; programs.mako = { enable = true; backgroundColor = solarized.base03; borderColor = solarized.base02; textColor = solarized.base0; progressColor = "over ${solarized.base02}"; groupBy = "app-name"; defaultTimeout = 5000; }; systemd.user.services.mako = { Unit.PartOf = [ "sway-session.target" ]; Install.WantedBy = [ "sway-session.target" ]; Service = { ExecStartPre = toString (pkgs.writeShellScript "reset-mako-inhibit-state" '' set -e mkdir -p "$(dirname ${lib.escapeShellArg makoInhibitStateFile})" echo 0 > ${lib.escapeShellArg makoInhibitStateFile} ''); ExecStart = "${pkgs.mako}/bin/mako"; Restart = "on-failure"; }; }; services.kanshi = { enable = true; profiles = let getMachineConfig = machine: machines: if lib.hasAttr machine machines then lib.getAttr machine machines else { }; in getMachineConfig (nixosConfig.networking.hostName) { nunotaba = { standalone.outputs = lib.singleton { criteria = "eDP-1"; mode = "1920x1080"; position = "0,0"; }; home.outputs = [ { criteria = "eDP-1"; mode = "1920x1080"; position = "0,424"; } { criteria = "Dell Inc. DELL U2410 F525M0A104NL"; mode = "1920x1200"; position = "1920,0"; } ]; }; }; }; services.redshift = { enable = true; package = pkgs.redshift-wlr; extraOptions = [ "-v" "-m" "wayland" ]; latitude = "49.52"; longitude = "10.17"; temperature = { day = 6500; night = 3500; }; }; }