nixos-config/users/simon/modules/sway.nix
Simon Bruder 09618443eb
sway: Manually implement systemd/dbus integration
The way provided by home-manager does not work reliably.

This also references (hopefully) all executables called by systemd
services by their full path.
2021-05-03 10:16:58 +02:00

631 lines
22 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{ 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;
# TODO: remove with 21.05
package = pkgs.unstable.sway;
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
'' + (
let
environmentVariables = lib.concatStringsSep " " [
"DBUS_SESSION_BUS_ADDRESS"
"DISPLAY"
"SWAYSOCK"
"WAYLAND_DISPLAY"
];
in
''
# From https://github.com/swaywm/sway/wiki#gtk-applications-take-20-seconds-to-start
exec systemctl --user import-environment ${environmentVariables} && \
hash dbus-update-activation-environment 2>/dev/null && \
dbus-update-activation-environment --systemd ${environmentVariables} && \
systemctl --user start sway-session.target
''
);
};
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.targets.sway-session = {
Unit = {
Description = "sway compositor session";
Documentation = [ "man:systemd.special(7)" ];
BindsTo = [ "graphical-session.target" ];
Wants = [ "graphical-session-pre.target" ];
After = [ "graphical-session-pre.target" ];
};
};
systemd.user.services.swayidle = {
Unit.PartOf = [ "sway-session.target" ];
Install.WantedBy = [ "sway-session.target" ];
Service = {
# swayidle requires sh and swaymsg to be in path
Environment = "PATH=${pkgs.bash}/bin:${config.wayland.windowManager.sway.package}/bin";
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 __NIXOS_SET_ENVIRONMENT_DONE # 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 doesnt work since the defaults overwrite it
commonConfig = ''
background=fdf6e3
border-bottom=eee8d5
border=eee8d5
button-background=fdf6e3
button-text=657b83
'';
in
''
font=Monospace 12
[warning]
text=b58900
${commonConfig}
[error]
text=dc322f
${commonConfig}
'';
# home-managers 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 [ "$(${pkgs.coreutils}/bin/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="$(${pkgs.coreutils}/bin/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 = "${pkgs.brightnessctl}/bin/brightnessctl -q set +5%";
on-scroll-down = "${pkgs.brightnessctl}/bin/brightnessctl -q set 5%-";
};
mpd = {
server = config.services.mpd.network.listenAddress;
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 -h ${config.services.mpd.network.listenAddress} volume +2";
on-scroll-down = "${pkgs.mpc_cli}/bin/mpc -q -h ${config.services.mpd.network.listenAddress} 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 = "${pkgs.pavucontrol}/bin/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 = "${config.programs.alacritty.package}/bin/alacritty -e ${pkgs.networkmanager}/bin/nmtui";
};
"custom/vpn" = {
interval = 10;
exec = pkgs.writeShellScript "vpn-state" ''
${pkgs.iproute}/bin/ip -j link \
| ${pkgs.jq}/bin/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 = "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>";
};
};
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
${pkgs.coreutils}/bin/mkdir -p "$(${pkgs.coreutils}/bin/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;
};
};
}