My main Questions
Thank you so much for your time and patience ![]()
- Should I split these topics into separate help threads?
- Am I even taking the right approach? Would it be simpler if I installed all the window manager packages with the hostâs
aptand only did the configurations withpackage = nullin home-manager? - How do I solve the issues I (presumably) have with DBus?
How can I get theme switching fully working, asgsettingsdoesnât affect all (but most) apps?- Am I doing the scaling correctly?
- Am I doing the guarding for the services with regards to sway vs. i3 correctly?
What I am doing
I have a machine that (has to) run Debian Trixie with KDE/Plasma/SDDM. I use home-manager in standalone mode to install and configure sway and i3 alongside the hostâs Plasma session.
Most of it works, but some things donât work and other problems were likely not solved âcorrectlyâ. I had to brute force a few âsolutionsâ since I didnât find enough information online.
nix --version: nix (Nix) 2.33.1
Some of the flakes and how I set them up
In my dotfiles, these are some of the details of how I set things up. I am sure it contains non-sensical or wrong stuff, since I am not yet confident with nix or home-manager. I am happy about any and all pointers on how I can improve things.
hosts/schenck-debian/default.nix
hosts/schenck-debian/default.nix (excerpt):
{
config,
lib,
...
}:
{
targets.genericLinux.enable = true;
home.file.".config/plasma-workspace/env/10-home-manager-xdg-data-dirs.sh" = {
executable = true;
text = ''
#!/bin/sh
export XDG_DATA_DIRS="${config.home.sessionVariables.XDG_DATA_DIRS}"
'';
};
home.sessionVariables = {
XDG_CONFIG_HOME = "$HOME/.config";
XDG_CACHE_HOME = "$HOME/.cache";
XDG_DATA_HOME = "$HOME/.local/share";
XDG_STATE_HOME = "$HOME/.local/state";
};
systemd.user.sessionVariables = {
XDG_CONFIG_HOME = "$HOME/.config";
XDG_CACHE_HOME = "$HOME/.cache";
XDG_DATA_HOME = "$HOME/.local/share";
XDG_STATE_HOME = "$HOME/.local/state";
};
systemd.user = {
services.polkit-kde-agent = {
# A polkit agent for graphical privilege escalation.
Unit = {
Description = "KDE Polkit Authentication Agent";
After = [
"graphical-session.target"
"dbus.service"
];
PartOf = [ "graphical-session.target" ];
# Only start in sway and i3, not e.g. plasma:
ExecCondition = "/bin/bash -c '[ $XDG_CURRENT_DESKTOP = sway ] || [ $XDG_CURRENT_DESKTOP = i3 ]'";
};
Service = {
Type = "simple";
ExecStart = "/usr/lib/x86_64-linux-gnu/libexec/polkit-kde-authentication-agent-1";
Restart = "on-failure";
RestartSec = 1;
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
};
};
wayland.windowManager.sway = {
config = {
output = {
"HDMI-A-1" = {
scale = "2";
bg = "${../../assets/images/daniel-leone-v7daTKlZzaw-unsplash.jpg} fill";
};
};
};
};
# Uses home-manager standalone module on debian linux:
imports = [
./screencast.nix
../../home/linux-desktop.nix
../../home/standalone.nix
];
}
home/linux-desktop.nix
home/linux-desktop.nix (excerpt):
# Scaling in xresources:
xresources = {
extraConfig = ''
*dpi: 192
Xft.dpi: 192
Xcursor.size: 48
'';
};
home.file.".local/bin/lightmode.sh" = {
executable = true;
text = # bash
''
#!/usr/bin/env bash
printf "light" > ~/.config/current_theme_store
gsettings set org.gnome.desktop.interface gtk-theme 'Breeze'
gsettings set org.gnome.desktop.interface icon-theme 'breeze'
gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
tmux source-file ~/.config/tmux/tmux.conf
pkill -USR1 zsh
'';
};
home.file.".local/bin/darkmode.sh" = {
executable = true;
text = # bash
''
#!/usr/bin/env bash
printf "dark" > ~/.config/current_theme_store
gsettings set org.gnome.desktop.interface gtk-theme 'Breeze-Dark'
gsettings set org.gnome.desktop.interface icon-theme 'breeze-dark'
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
tmux source-file ~/.config/tmux/tmux.conf
pkill -USR1 zsh
'';
};
configs/i3-sway/sway.nix
{ lib, pkgs, ... }:
{
home.file.".local/bin/start-sway.sh" = {
executable = true;
text = # bash
''
#!/usr/bin/env bash
# Session
export XDG_SESSION_TYPE=wayland
export XDG_SESSION_DESKTOP=sway
export XDG_CURRENT_DESKTOP=sway
# Wayland stuff
export MOZ_ENABLE_WAYLAND=1
export QT_QPA_PLATFORM=wayland
export SDL_VIDEODRIVER=wayland
export _JAVA_AWT_WM_NONREPARENTING=1
export NIXOS_OZONE_WL=1
exec ${pkgs.sway}/bin/sway "$@"
'';
};
wayland.windowManager.sway = {
enable = true;
# This also starts a systemd sway-session.target:
systemd.enable = true;
wrapperFeatures.gtk = true; # Include fixes for GTK apps under Sway
config =
let
commonConfig = import ./common-config.nix {
inherit lib pkgs;
commands = {
powerCommand = "exec ~/.local/bin/powermenu.sh fuzzel";
settingsCommand = "exec ~/.local/bin/settingsmenu.sh fuzzel";
notificationDismissCommand = "exec ${pkgs.mako}/bin/makoctl dismiss";
applicationCommand = "exec ${pkgs.fuzzel}/bin/fuzzel";
emojiCommand = "exec ${pkgs.bemoji}/bin/bemoji --type";
audioUpCommand = "exec ${pkgs.avizo}/bin/volumectl -u up";
audioDownCommand = "exec ${pkgs.avizo}/bin/volumectl -u down";
audioMuteCommand = "exec ${pkgs.avizo}/bin/volumectl toggle-mute";
audioMicMuteCommand = "exec ${pkgs.avizo}/bin/volumectl -m toggle-mute";
brightnessUpCommand = "exec ${pkgs.avizo}/bin/lightctl up";
brightnessDownCommand = "exec ${pkgs.avizo}/bin/lightctl down";
searchCommand = "exec ${pkgs.fuzzel}/bin/fuzzel";
};
};
in
lib.recursiveUpdate commonConfig {
startup = [
{
command = ''
swayidle -w \
timeout 300 'swaylock -fF' \
timeout 600 'swaymsg "output * dpms off"' \
resume 'swaymsg "output * dpms on"' \
before-sleep 'swaylock -fF'
'';
}
{
command = "nm-applet";
}
];
seat = {
"*" = {
hide_cursor = "when-typing enable";
};
};
input = {
"type:keyboard" = {
# Map capslock to escape:
"xkb_options" = "caps:escape";
"repeat_delay" = "200";
"repeat_rate" = "50";
};
};
bars = [
{
command = "${pkgs.waybar}/bin/waybar";
}
];
};
extraConfig = # swayconfig
''
for_window [shell="xwayland"] title_format "[XWayland] %title"
# Make sure tmux-server, etc. exit when sway exits so that new sessions
# start with new servers that attach to the correct dbus, etc.
exec --no-startup-id swaymsg -mt subscribe '[]' || true && ${pkgs.tmux}/bin/tmux kill-server
exec --no-startup-id swaymsg -mt subscribe '[]' || true && pkill ghostty
exec --no-startup-id swaymsg -mt subscribe '[]' || true && systemctl --user stop mako.service
'';
};
}
configs/i3-sway/i3.nix
{ lib, pkgs, ... }:
{
home.file.".local/bin/start-i3.sh" = {
executable = true;
text = # bash
''
#!/usr/bin/env bash
unset MOZ_ENABLE_WAYLAND
unset QT_QPA_PLATFORM
unset SDL_VIDEODRIVER
unset _JAVA_AWT_WM_NONREPARENTING
unset NIXOS_OZONE_WL
unset WAYLAND_DISPLAY
unset XDG_SESSION_TYPE
unset XDG_SESSION_DESKTOP
unset XDG_CURRENT_DESKTOP
export XDG_SESSION_TYPE=x11
export XDG_SESSION_DESKTOP=i3
export XDG_CURRENT_DESKTOP=i3
exec ${pkgs.i3}/bin/i3 "$@"
'';
};
xsession.windowManager.i3 = {
enable = true;
config =
let
commonConfig = import ./common-config.nix {
inherit lib pkgs;
commands = {
powerCommand = "exec ~/.local/bin/powermenu.sh rofi";
settingsCommand = "exec ~/.local/bin/settingsmenu.sh rofi";
# dunst manages its shortcut itself:
notificationDismissCommand = null;
applicationCommand = "exec ${pkgs.rofi}/bin/rofi -show drun";
emojiCommand = null;
audioUpCommand = "exec --no-startup-id ${pkgs.pulseaudio}/bin/pactl set-sink-volume 0 +5%";
audioDownCommand = "exec --no-startup-id ${pkgs.pulseaudio}/bin/pactl set-sink-volume 0 -5%";
audioMuteCommand = "exec --no-startup-id ${pkgs.pulseaudio}/bin/pactl set-sink-mute 0 toggle";
audioMicMuteCommand = null;
brightnessUpCommand = "exec --no-startup-id ${pkgs.xbacklight}/bin/xbacklight -inc 20";
brightnessDownCommand = "exec --no-startup-id ${pkgs.xbacklight}/bin/xbacklight -dec 20";
searchCommand = "exec ${pkgs.rofi}/bin/rofi -show drun";
};
};
in
lib.recursiveUpdate commonConfig {
startup = [
{
command = "${pkgs.xautolock}/bin/xautolock -time 5 -locker \"i3lock -n -c 191724\"";
notification = false;
}
{
# Turn off screen, pc:
command = "xset dpms 600 900 1200";
notification = false;
}
{
command = "${pkgs.feh}/bin/feh --no-fehbg --bg-fill ${../../assets/images/daniel-leone-v7daTKlZzaw-unsplash.jpg}";
always = true;
notification = false;
}
{
# Keyboard repeat rate:
command = "${pkgs.xset}/bin/xset r rate 200 50";
notification = false;
}
{
# Map CapsLock to Escape:
command = "${pkgs.setxkbmap}/bin/setxkbmap -option caps:escape";
notification = false;
}
{
command = "systemctl --user restart polybar.service";
always = true;
notification = false;
}
{
command = "nm-applet";
notification = false;
}
{
command = "blueman-applet";
notification = false;
}
];
# Polybar is started by start command above using systemctl.
bars = [ ];
};
extraConfig = # i3config
''
# Ensure borders for _all_ windows. Without this, ghostty and vivaldi
# wouldn't have borders, for example.
for_window [class="^.*"] border pixel 1
# Update environment same as in sway module of home-manager:
exec --no-startup-id ${pkgs.dbus}/bin/dbus-update-activation-environment --systemd DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE XDG_SESSION_DESKTOP XCURSOR_THEME XCURSOR_SIZE; systemctl --user reset-failed
# Make sure tmux-server, etc. exit when i3 exits so that new sessions
# start with new servers that attach to the correct dbus, etc.
exec --no-startup-id ${pkgs.i3}/bin/i3-msg -mt subscribe '[]' || true && ${pkgs.tmux}/bin/tmux kill-server
exec --no-startup-id ${pkgs.i3}/bin/i3-msg -mt subscribe '[]' || true && pkill ghostty
exec --no-startup-id ${pkgs.i3}/bin/i3-msg -mt subscribe '[]' || true && systemctl --user stop polybar.service; systemctl --user stop dunst.service
'';
};
}
configs/i3-sway/common-config.nix (likely uninteresting)
{
lib,
pkgs,
commands,
}:
let
terminal = "${pkgs.ghostty}/bin/ghostty";
in
{
focus = {
followMouse = false;
};
fonts = {
names = [
"Monaspace Neon"
"OpenMoji Color"
"Symbols Nerd Font Mono"
];
size = 13.0;
};
# For any defaults left from the sway/i3 modules in home manager:
modifier = "Mod4"; # Use the Super/Windows key as the Mod key
terminal = terminal;
gaps = {
outer = 6;
inner = 6;
};
window = {
titlebar = false;
border = 1;
};
defaultWorkspace = "workspace number 1";
colors = {
background = "#191724";
focused = {
border = "#A19DD4";
background = "#191724";
text = "#A2A2A9";
indicator = "#C394C2";
childBorder = "#A19DD4";
};
focusedInactive = {
border = "#84848C";
background = "#0B0A0F";
text = "#878794";
indicator = "#37363E";
childBorder = "#37363E";
};
unfocused = {
border = "#0B0A0F";
background = "#0B0A0F";
text = "#878794";
indicator = "#37363E";
childBorder = "#37363E";
};
placeholder = {
background = "#191724";
text = "#A2A2A9";
# unused
border = "#000000";
indicator = "#000000";
childBorder = "#000000";
};
};
keybindings = lib.mkOptionDefault {
"Mod4+Shift+a" = "focus child";
"Mod4+n" = lib.mkIf (
commands.notificationDismissCommand != null
) commands.notificationDismissCommand;
"Mod4+Shift+e" = lib.mkIf (commands.powerCommand != null) commands.powerCommand;
"Mod4+Shift+s" = lib.mkIf (commands.settingsCommand != null) commands.settingsCommand;
"Mod4+d" = lib.mkIf (commands.applicationCommand != null) commands.applicationCommand;
"Mod4+Shift+d" = lib.mkIf (commands.emojiCommand != null) commands.emojiCommand;
# Commands provided by avizo.service
"XF86AudioRaiseVolume" = commands.audioUpCommand;
"XF86AudioLowerVolume" = commands.audioDownCommand;
"XF86AudioMute" = commands.audioMuteCommand;
"XF86AudioMicMute" = lib.mkIf (commands.audioMicMuteCommand != null) commands.audioMicMuteCommand;
"XF86MonBrightnessUp" = commands.brightnessUpCommand;
"XF86MonBrightnessDown" = commands.brightnessDownCommand;
"XF86AudioPlay" = "exec playerctl play-pause";
"XF86AudioPause" = "exec playerctl play-pause";
"XF86AudioNext" = "exec playerctl next";
"XF86AudioPrev" = "exec playerctl previous";
"XF86AudioStop" = "exec playerctl stop";
"XF86Search" = lib.mkIf (commands.searchCommand != null) commands.searchCommand;
# More or less default:
"Mod4+1" = "workspace number 1";
"Mod4+0" = "workspace number 10";
"Mod4+2" = "workspace number 2";
"Mod4+3" = "workspace number 3";
"Mod4+4" = "workspace number 4";
"Mod4+5" = "workspace number 5";
"Mod4+6" = "workspace number 6";
"Mod4+7" = "workspace number 7";
"Mod4+8" = "workspace number 8";
"Mod4+9" = "workspace number 9";
"Mod4+Down" = "focus down";
"Mod4+Left" = "focus left";
"Mod4+Return" = "exec ${terminal}";
"Mod4+Right" = "focus right";
"Mod4+Shift+0" = "move container to workspace number 10";
"Mod4+Shift+1" = "move container to workspace number 1";
"Mod4+Shift+2" = "move container to workspace number 2";
"Mod4+Shift+3" = "move container to workspace number 3";
"Mod4+Shift+4" = "move container to workspace number 4";
"Mod4+Shift+5" = "move container to workspace number 5";
"Mod4+Shift+6" = "move container to workspace number 6";
"Mod4+Shift+7" = "move container to workspace number 7";
"Mod4+Shift+8" = "move container to workspace number 8";
"Mod4+Shift+9" = "move container to workspace number 9";
"Mod4+Shift+Down" = "move down";
"Mod4+Shift+Left" = "move left";
"Mod4+Shift+Right" = "move right";
"Mod4+Shift+Up" = "move up";
"Mod4+Shift+c" = "reload";
"Mod4+Shift+h" = "move left";
"Mod4+Shift+j" = "move down";
"Mod4+Shift+k" = "move up";
"Mod4+Shift+l" = "move right";
"Mod4+Shift+minus" = "move scratchpad";
"Mod4+Shift+q" = "kill";
"Mod4+Shift+space" = "floating toggle";
"Mod4+Up" = "focus up";
"Mod4+a" = "focus parent";
"Mod4+b" = "splith";
"Mod4+e" = "layout toggle split";
"Mod4+f" = "fullscreen toggle";
"Mod4+h" = "focus left";
"Mod4+j" = "focus down";
"Mod4+k" = "focus up";
"Mod4+l" = "focus right";
"Mod4+minus" = "scratchpad show";
"Mod4+r" = "mode resize";
"Mod4+s" = "layout stacking";
"Mod4+space" = "focus mode_toggle";
"Mod4+v" = "splitv";
"Mod4+w" = "layout tabbed";
};
}
Window Manager Issues
Window Manager Issues
I have found out that I cannot use home-managerâs swaylock or i3lock, as they would always say the password was typed incorrectly. Instead, those two packages I have to install with apt install and then I configured swaylock with package = null;.
I would prefer to set up everything consistently, ideally with everything being in home-manager.
- How can I get
swaylockandi3lockworking, if I want to use the home-manager packages?
I got screensharing to work, but full-screen only.
- Is it possible to only share an appâs window under wayland and sway? Do I have to wait for sway 1.12?
DBus Issues
DBus Issues
I would expect logging out of and back into a session shouldnât be a problem. E.g. boot SDDM â log into i3 â exit to SDDM â log into i3
Sometimes (not always), when exiting sway or i3 and afterwards logging into sway or i3 (same or different one), I have problems like the following which I assume are caused by DBus:
- No waybar
- Some error that the tray cannot register on the bus
- Ghostty only starts after a huge delay (Konsole as well)
I think maybe the issue appeared more frequently when I ran home-manager switch in the session, but I am not sure about this and it could also be coincidental since the sample size is rather small.
What I tried (and failed):
- Starting i3 (or sway) with
dbus-start-session -- ${pkgs.i3}/bin/i3- When doing that, Home Manager would give me a warning when switching. It uses the bus
unix:path=/run/user/1000/bus, but${DBUS_SESSION_BUS_ADDRESS}is set to another bus (with a random, unique ID).
- When doing that, Home Manager would give me a warning when switching. It uses the bus
- Enabling xsession in home-manager:
- Adding the update of the busâ ENV variables to i3 (the same way it is done in sway by home manager after enabling
wayland.windowManager.sway.systemd.enable = true;), by doing:-
extraConfig = # i3config '' # Update environment same as in sway module of home-manager: exec --no-startup-id ${pkgs.dbus}/bin/dbus-update-activation-environment --systemd DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE XDG_SESSION_DESKTOP XCURSOR_THEME XCURSOR_SIZE; systemctl --user reset-failed '';
-
- Tearing stuff down on exit:
-
extraConfig = # i3config '' # Make sure tmux-server, etc. exit when i3 exits so that new sessions # start with new servers that attach to the correct dbus, etc. exec --no-startup-id ${pkgs.i3}/bin/i3-msg -mt subscribe '[]' || true && ${pkgs.tmux}/bin/tmux kill-server exec --no-startup-id ${pkgs.i3}/bin/i3-msg -mt subscribe '[]' || true && pkill ghostty exec --no-startup-id ${pkgs.i3}/bin/i3-msg -mt subscribe '[]' || true && systemctl --user stop polybar.service; systemctl --user stop dunst.service '';
-
Do these things even make sense or am I doing nonsensical stuff?
I assume the DBus is causing this issue, but I have no idea how to go about it.
Scaling
Scaling
I am on a HiDPI screen where I want everything to scale 2x. In sway/wayland thatâs easily achievable with:
wayland.windowManager.sway = {
config = {
output = {
"HDMI-A-1" = {
scale = "2";
};
};
};
};
However, sometime the mouse cursor gets tiny when it is over specific apps. Especially in todoist-electron. Whatâs special about todoist-electron? How can I make sure the cursor is the same size there?
To achieve the same in X11/i3, I do:
-
xresources = { extraConfig = '' *dpi: 192 Xft.dpi: 192 Xcursor.size: 48 ''; };
Do I even need *dpi?
I used to also do:
-
xsession = { enable = true; profileExtra = '' # Scaling export QT_ENABLE_HIGHDPI_SCALING=1 export QT_SCALE_FACTOR=2 export GDK_SCALE=2 ''; };
But that would also lead to all kinds of DBus/Session problems with i3. I removed the whole block and it doesnât seem to affect QT or GDK scaling, as everything is still scaled correctly in sway and i3.
Themeing ('solved')
Themeing
This works âgood enoughâ now. After playing around in KDEâs system settings, all apps except Vivaldi now follow what I set with
gsettings.
I want to be able to switch between light and dark mode. For that, I created scripts like this one for light mode (see `home-linux.nix above for more context):
#!/usr/bin/env bash
gsettings set org.gnome.desktop.interface gtk-theme 'Breeze'
gsettings set org.gnome.desktop.interface icon-theme 'breeze'
gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
# The rest works as expected
# Sourced by command line tools via aliases (works):
printf "light" > ~/.config/current_theme_store
# Updates the running tmux session (works):
tmux source-file ~/.config/tmux/tmux.conf
# Updates ZSH theme (works):
pkill -USR1 zsh
I would expect all apps to update their theme, but they arenât. Some apps fully switch over, for example Firefox, Dolphin (file explorer), or ghostty. Vivaldi switches the pages it shows to light mode (e.g. this discourse), but the interface itself doesnât (even though I set different themes for âOS dark and lightâ). pavucontrol for example doesnât switch at all. It stays in dark mode.
I installed the following packages with home-manager: kdePackages.breeze, kdePackages.breeze-gtk, kdePackages.breeze-icons
- Does this have to do with the underlying Plasma? Am I doing it wrong? What am I missing?
Fonts in Todoist
Fonts in todoist-electron
For some reason, the app displays most of the time but not always the wrong fonts (some ugly serif font). It appears it can neither pick up the bundled fonts, nor the system fonts.
How can I make sure Todoist uses the system fonts (or the bundled ones)? Does this have to do with the fact that it is based on an AppImage or is that unrelated?
Systemd Units' Guards
Systemd Unitsâ Guards
Since I configure some services with home-manager, home-manager also manages the unit file. Two examples are mako and dunst. I only want to run dunst when in i3 and I only want to run mako when in sway. What I did works, but I wonder if it is even the right way to do this. Having the line at the end of the wmâs config that waits for the exit and then stops the service feels hacky in a way.
Mako
In sway.nix:
wayland.windowManager.sway.systemd.enable = true;
In mako.nix:
systemd.user.services.mako = {
Unit = {
Description = "Lightweight Wayland notification daemon";
Documentation = "man:mako(1)";
PartOf = lib.mkForce [ "sway-session.target" ];
After = lib.mkForce [ "sway-session.target" ];
};
Service = {
Type = "dbus";
BusName = "org.freedesktop.Notifications";
ExecCondition = "/bin/sh -c '[ -n \"$WAYLAND_DISPLAY\" ]'";
ExecStart = "${pkgs.mako}/bin/mako";
ExecReload = "${pkgs.mako}/bin/makoctl reload";
};
Install = {
WantedBy = lib.mkForce [ "sway-session.target" ];
};
};
End of sway config:
exec --no-startup-id swaymsg -mt subscribe '[]' || true && systemctl --user stop mako.service
Dunst
In my dunst.nix file I added:
systemd.user.services.dunst.Service.ExecCondition =
"/bin/bash -c '[ \"$XDG_CURRENT_DESKTOP\" = \"i3\" ]'";
$XDG_CURRENT_DESKTOP is set by the wrapper script that starts i3:
#!/usr/bin/env bash
unset MOZ_ENABLE_WAYLAND
unset QT_QPA_PLATFORM
unset SDL_VIDEODRIVER
unset _JAVA_AWT_WM_NONREPARENTING
unset NIXOS_OZONE_WL
unset WAYLAND_DISPLAY
unset XDG_SESSION_TYPE
unset XDG_SESSION_DESKTOP
unset XDG_CURRENT_DESKTOP
export XDG_SESSION_TYPE=x11
export XDG_SESSION_DESKTOP=i3
export XDG_CURRENT_DESKTOP=i3
exec ${pkgs.i3}/bin/i3 "$@"
End of i3 config:
exec --no-startup-id ${pkgs.i3}/bin/i3-msg -mt subscribe '[]' || true && systemctl --user stop polybar.service; systemctl --user stop dunst.service