feat(desktop): nwg-displays writes niri outputs, kanshi dropped
The port swapped moonarch's wdisplays-persistent for the nixpkgs wdisplays, which cannot save -- so the kanshi config stayed empty and its unit was skipped on every boot. nwg-displays supports niri and writes the layout to ~/.config/niri/monitor.kdl, which niri applies itself. niri prefers a home config and does not merge, so config.kdl is seeded with an include of /etc/niri/config.kdl, like moonarch does for stasis.rune.
This commit is contained in:
1 parent
058ec5c98d
commit
f4b8b5dd50
4 files changed
+66
-37
No files matched your search
@@ -1,5 +1,40 @@
|
||||
# Decisions
|
||||
|
||||
## 2026-09-08 – nwg-displays replaces wdisplays; kanshi dropped for niri's own outputs
|
||||
|
||||
- **Who**: Dominik, ClaudeCode
|
||||
- **Why**: Monitors could not be set persistently at all. moonarch pairs
|
||||
`wdisplays-persistent` (AUR fork `sfs-pra/wdisplays`, see moonarch `README.md:25` and
|
||||
its `DECISIONS.md:178`) with kanshi: the fork saves straight into the kanshi config.
|
||||
The port took the nixpkgs `wdisplays` instead — upstream, with no save function — and
|
||||
never recorded the difference. The result was a GUI that applies a layout and forgets
|
||||
it, an always-empty `~/.config/kanshi/config`, and a `kanshi.service` whose
|
||||
`ConditionPathExists` therefore skipped it on every boot.
|
||||
- **Tradeoffs**: Packaging the fork would match moonarch exactly and keeps kanshi's
|
||||
per-combination profiles, but it is unmaintained and would be ours to carry.
|
||||
`nwg-displays` is in nixpkgs, actively developed, and supports niri as a first-class
|
||||
target alongside sway and Hyprland; it writes `~/.config/niri/monitor.kdl` and adds the
|
||||
`include` for it. That makes kanshi redundant — niri applies `output` blocks itself, on
|
||||
hotplug and with live reload — so it goes, together with the hardening set it had just
|
||||
been given. What is given up is kanshi's profiles *per attached combination*: niri has
|
||||
one setting per output. Fixed monitors do not care; several docks with different
|
||||
arrangements would, and then kanshi has to come back.
|
||||
The seed is the same mechanism moonarch chose for stasis (its `DECISIONS.md`,
|
||||
2026-04-22): niri has no config hierarchy either, so system-default-plus-user-override
|
||||
cannot be modelled — the home file is seeded once and then belongs to the user.
|
||||
- **How**: `modules/desktop.nix` — `wdisplays` → `nwg-displays`, `kanshi` dropped from
|
||||
`environment.systemPackages`, new `niriUserConfig` in the `let` block holding
|
||||
`include "/etc/niri/config.kdl"`, seeded through the existing
|
||||
`systemd.user.tmpfiles.rules` block with `C` so later edits survive. The stale comment
|
||||
about the undeployed kanshi config is gone from `environment.etc`, the paragraph in
|
||||
`README.md` rewritten. `modules/services.nix` — `systemd.user.services.kanshi` removed
|
||||
and `wlsunset`'s `after` reduced to `graphical-session.target`.
|
||||
Verified: niri 26.04 does load `include` (an include pointing at a deliberately broken
|
||||
file makes `niri validate` fail on that file, so it is parsed, not ignored);
|
||||
`nwg-displays` 0.4.4 logs `Running on niri` and sees both outputs; without
|
||||
`~/.config/niri/` it exits 0 with no window, and it creates `monitor.kdl` but no
|
||||
`config.kdl` — which is what the seed is for.
|
||||
|
||||
## 2026-08-24 – power-profiles-daemon replaces auto-cpufreq
|
||||
|
||||
- **Who**: Dominik, ClaudeCode
|
||||
|
||||
@@ -257,11 +257,13 @@ NixOS module owns the destination:
|
||||
| `waypaper/config.ini` | wallpaper path, rewritten and seeded into `$HOME` |
|
||||
| fontconfig defaults | `/etc/fonts/conf.d` is owned by the NixOS module |
|
||||
|
||||
`kanshi/config` is not deployed: the file in the config source is empty, and
|
||||
monitor profiles are machine specific — they belong in
|
||||
`~/.config/kanshi/config`. A missing config file is what kanshi treats as an
|
||||
error (`failed to parse config file`), which with `Restart=on-failure` would
|
||||
spin, so the service starts only once that file exists.
|
||||
`kanshi/config` is not deployed and kanshi is not installed: niri configures its
|
||||
outputs itself, and `nwg-displays` writes the layout it is given to
|
||||
`~/.config/niri/monitor.kdl`. niri prefers a home config over the deployed one
|
||||
and does not merge them, so `~/.config/niri/config.kdl` is seeded with an
|
||||
`include` of `/etc/niri/config.kdl` — the home config then adds the monitor
|
||||
layout instead of replacing everything. nwg-displays also refuses to start
|
||||
without that file.
|
||||
|
||||
Programs that read only from `$HOME` and have no system-wide fallback get a
|
||||
copy seeded through `systemd.user.tmpfiles` with the `C` directive, which never
|
||||
|
||||
+18
-7
@@ -36,6 +36,20 @@ let
|
||||
--replace-fail /usr/share/moonarch/wallpaper-blur.jpg ${wallpaperBlur}
|
||||
'';
|
||||
|
||||
# niri prefers ~/.config/niri/config.kdl over /etc/niri/config.kdl and does not
|
||||
# merge the two, so a home config would otherwise replace the whole deployed
|
||||
# one. This seed pulls the system config back in through `include`, which niri
|
||||
# 26.04 really does load (verified by pointing an include at a broken file and
|
||||
# watching `niri validate` fail on it).
|
||||
#
|
||||
# It exists for nwg-displays, which writes the output layout to
|
||||
# ~/.config/niri/monitor.kdl and adds an include for it here. Without a
|
||||
# config.kdl in the home directory nwg-displays refuses to start at all
|
||||
# ("Couldn't find niri config directory", exit 0, no window).
|
||||
niriUserConfig = pkgs.writeText "niri-config.kdl" ''
|
||||
include "/etc/niri/config.kdl"
|
||||
'';
|
||||
|
||||
# PipeWire searches $PIPEWIRE_CONFIG_DIR, then $XDG_CONFIG_HOME/pipewire, then
|
||||
# its compiled-in /etc/pipewire — never /etc/xdg (verified in PipeWire's
|
||||
# src/pipewire/conf.c). The shared file therefore cannot be deployed the way
|
||||
@@ -93,11 +107,6 @@ in
|
||||
"xdg/quickshell/moonarch".source = "${xdg}/quickshell/moonarch";
|
||||
"xdg/foot/foot.ini".source = "${xdg}/foot/foot.ini";
|
||||
"xdg/fastfetch/config.jsonc".source = "${xdg}/fastfetch/config.jsonc";
|
||||
# defaults/xdg/kanshi/config is an empty placeholder. Not
|
||||
# deployed: monitor profiles are machine specific and belong in
|
||||
# ~/.config/kanshi/config. An empty file parses fine; a missing one is what
|
||||
# kanshi reports as "failed to parse config file", which is why the unit in
|
||||
# modules/services.nix waits for the home config to exist.
|
||||
"xdg/Kvantum/kvantum.kvconfig".source = "${xdg}/Kvantum/kvantum.kvconfig";
|
||||
"xdg/qt6ct/qt6ct.conf".source = "${xdg}/qt6ct/qt6ct.conf";
|
||||
"xdg/gtk-3.0/settings.ini".source = "${xdg}/gtk-3.0/settings.ini";
|
||||
@@ -286,7 +295,6 @@ in
|
||||
|
||||
# Compositor extras
|
||||
xwayland-satellite
|
||||
kanshi
|
||||
|
||||
# Bar, notifications, clipboard.
|
||||
# Neither waybar nor swaync are installed: the Quickshell bar owns both
|
||||
@@ -361,7 +369,7 @@ in
|
||||
fwupd
|
||||
upower
|
||||
polkit_gnome
|
||||
wdisplays
|
||||
nwg-displays
|
||||
wl-color-picker
|
||||
|
||||
stasis
|
||||
@@ -399,6 +407,9 @@ in
|
||||
# exists, so the moonarch config has to be in place before it starts.
|
||||
"C %h/.config/stasis/stasis.rune 0644 - - - ${xdg}/stasis/stasis.rune"
|
||||
"C %h/.config/swaylock/config 0644 - - - ${swaylockConfig}"
|
||||
# nwg-displays needs this file to exist before it will start, and niri would
|
||||
# otherwise ignore the monitor.kdl it writes next to it.
|
||||
"C %h/.config/niri/config.kdl 0644 - - - ${niriUserConfig}"
|
||||
"C %h/.zshrc 0644 - - - ${userZshrc}"
|
||||
];
|
||||
}
|
||||
+6
-25
@@ -112,30 +112,6 @@ in
|
||||
# --- Firewall, replaces the ufw calls in post-install.sh ---
|
||||
networking.firewall.enable = true;
|
||||
|
||||
# --- Session services ---
|
||||
#
|
||||
# Rebuilt rather than copied: the upstream units reference /usr/bin/,
|
||||
# which does not exist under Nix.
|
||||
|
||||
systemd.user.services.kanshi = {
|
||||
description = "Dynamic display configuration";
|
||||
partOf = [ "graphical-session.target" ];
|
||||
after = [ "graphical-session.target" ];
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
# Without a config kanshi exits with a parse error and, with
|
||||
# Restart=on-failure, spins. Monitor profiles are per machine.
|
||||
unitConfig.ConditionPathExists = "%h/.config/kanshi/config";
|
||||
# The config is read, never written, so ProtectSystem=strict is enough and
|
||||
# no exception for %h is needed. Not exercised at runtime here: this host
|
||||
# has no kanshi config, so the condition skips the unit.
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = lib.getExe pkgs.kanshi;
|
||||
Restart = "on-failure";
|
||||
RestartSec = 3;
|
||||
} // waylandClientHardening;
|
||||
};
|
||||
|
||||
# --- System services from defaults/etc/systemd/system/ ---
|
||||
#
|
||||
# Rebuilt rather than copied, same reason as the user units: the originals
|
||||
@@ -166,6 +142,11 @@ in
|
||||
} // sysfsWriterHardening;
|
||||
};
|
||||
|
||||
# --- Session services ---
|
||||
#
|
||||
# Rebuilt rather than copied: the upstream units reference /usr/bin/,
|
||||
# which does not exist under Nix.
|
||||
|
||||
# The bar, launcher and every popout. A unit rather than a spawn-at-startup so
|
||||
# a crash does not leave the session without a bar until the next login.
|
||||
systemd.user.services.quickshell = {
|
||||
@@ -262,7 +243,7 @@ in
|
||||
systemd.user.services.wlsunset = {
|
||||
description = "Wlsunset night light (blue light filter)";
|
||||
partOf = [ "graphical-session.target" ];
|
||||
after = [ "graphical-session.target" "kanshi.service" ];
|
||||
after = [ "graphical-session.target" ];
|
||||
wantedBy = [ ];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
|
||||
Reference in new issue
Block a user