Running Sway and friends with home-manager systemd user services
I've recently installed my personal laptop to NixOS. Long time i3 user I took this opportunity to use sway instead. To manage some of my home configuration files I'm using the excellent home-manager project.
X to Wayland
Moving from i3 to sway is simple because the configuration is compatible. Even if sway provide XWayland support for non Wayland applications I tried to find Wayland native alternatives for the following applications:
- dunst (notification daemon) -> mako
- rofi (application launcher) -> wofi
- dmenu (application launcher) -> bemenu
- rxvt-unicode (terminal) -> termite
Also, to manage displays automatically: kanshi
And finally waybar for a nicer status bar.
Starting sway in NixOS
At the time of this writing sway cannot be started from graphical login managers (but it should be fixed soon).
Anyway I'm used to login directly on the tty and run startx
, but now with sway
it is instead start-sway
.
NixOS configuration
In /etc/nixos/configuration.nix
I've added:
programs.sway.enable = true;
This does not only install the sway
package but also activate pam configuration
for swaylock
, installs some default fonts, enable opengl...
Home-manager configuration
Now that sway is installed globally, I wanted to start sway
and other tools
like waybar
, mako
and kanshi
as systemd user services.
One tricky thing is to properly share a D-Bus socket between all theses services. Hopefully I've found my answer on serverfault (read it for details).
Also waybar
needs to know where to find the sway
socket. sway
exports $SWAYSOCK
once started but it's only available inside sway
. From a tty running sway --get-socket-path
will return sway socket not detected
. As a workaround it is possible to determine
the socket location based on your user id and sway process id:
export SWAYSOCK=/run/user/$(id -u)/sway-ipc.$(id -u).$(pgrep -f 'sway$').sock
My home-manager configuration for sway is:
{ config, pkgs, ... }:
let
start-sway = pkgs.writeShellScriptBin "start-sway" ''
# first import environment variables from the login manager
systemctl --user import-environment
# then start the service
exec systemctl --user start sway.service
'';
start-waybar = pkgs.writeShellScriptBin "start-waybar" ''
export SWAYSOCK=/run/user/$(id -u)/sway-ipc.$(id -u).$(pgrep -f 'sway$').sock
${pkgs.waybar}/bin/waybar
'';
in {
home.packages = with pkgs; [
start-sway
wofi grim wl-clipboard imv slurp brightnessctl bemenu
];
systemd.user.sockets.dbus = {
Unit = {
Description = "D-Bus User Message Bus Socket";
};
Socket = {
ListenStream = "%t/bus";
ExecStartPost = "${pkgs.systemd}/bin/systemctl --user set-environment DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus";
};
Install = {
WantedBy = [ "sockets.target" ];
Also = [ "dbus.service" ];
};
};
systemd.user.services.dbus = {
Unit = {
Description = "D-Bus User Message Bus";
Requires = [ "dbus.socket" ];
};
Service = {
ExecStart = "${pkgs.dbus}/bin/dbus-daemon --session --address=systemd: --nofork --nopidfile --systemd-activation";
ExecReload = "${pkgs.dbus}/bin/dbus-send --print-reply --session --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig";
};
Install = {
Also = [ "dbus.socket" ];
};
};
systemd.user.services.sway = {
Unit = {
Description = "Sway - Wayland window manager";
Documentation = [ "man:sway(5)" ];
BindsTo = [ "graphical-session.target" ];
Wants = [ "graphical-session-pre.target" ];
After = [ "graphical-session-pre.target" ];
};
Service = {
Type = "simple";
ExecStart = "${pkgs.sway}/bin/sway";
Restart = "on-failure";
RestartSec = 1;
TimeoutStopSec = 10;
};
};
systemd.user.services.mako = {
Unit = {
Description = "Mako notification daemon";
PartOf = [ "graphical-session.target" ];
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
Service = {
Type = "dbus";
BusName = "org.freedesktop.Notifications";
ExecStart = "${pkgs.mako}/bin/mako";
RestartSec = 5;
Restart = "always";
};
};
systemd.user.services.kanshi = {
Unit = {
Description = "Kanshi dynamic display configuration";
PartOf = [ "graphical-session.target" ];
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStart = "${pkgs.kanshi}/bin/kanshi";
RestartSec = 5;
Restart = "always";
};
};
xdg.configFile."kanshi/config".text = ''
{
output eDP-1 mode 1920x1080 position 0,0
output "ViewSonic Corporation VP2770 SERIES T56131300326" mode 2560x1440 position 1920,0
}
{
output eDP-1 mode 1920x1080 position 0,0
}
'';
systemd.user.services.waybar = {
Unit = {
Description = "Wayland bar for Sway and Wlroots based compositors";
PartOf = [ "graphical-session.target" ];
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStart = "${start-waybar}/bin/start-waybar";
RestartSec = 5;
Restart = "always";
};
};
}
start-sway
is run from a TTY, it imports my shell environment in the user
systemd daemon and start sway.service
.
The sway.service
binds to graphical-session.target
so that when it is
started graphical-session.target
and all dependent services are started
as well:
⚡ systemctl --user list-dependencies sway.service
sway.service
● ├─-.slice
● └─graphical-session.target
● ├─kanshi.service
● ├─mako.service
● ├─waybar.service
● └─basic.target
● ├─paths.target
● ├─sockets.target
● │ ├─dbus.socket
● │ ├─gpg-agent-ssh.socket
● │ ├─gpg-agent.socket
● │ └─pulseaudio.socket
● └─timers.target
⚡ systemctl --user show-environment | grep DBUS
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
⚡ env | grep DBUS
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
If everything goes well you should be able to run:
⚡ nix-shell -p libnotify
[nix-shell:~]$ notify-send Test
And the notification should be properly displayed by mako
.