Files
moonix/modules/desktop.nix
T
nevaforget d26faa091d fix(desktop): file dialog on the GTK backend, one portal config
useNautilus = false assigns FileChooser to gtk and drops the nautilus D-Bus
registration; the GTK3 dialog then follows the same settings.ini as thunar.

The deployed niri-portals.conf is gone: /etc/xdg-desktop-portal/ ranks below
/etc/xdg/, where programs.niri writes the same file. Its one extra key,
Inhibit=none, moves to desktop.nix.
2026-09-25 13:10:16 +02:00

231 lines
11 KiB
Nix

# ABOUTME: Deploys the XDG configs from the moonarch repo and the package set.
# ABOUTME: Niri, Quickshell, terminal, portals and the packages without a theme.
{ pkgs, moonarchSrc, moonarchPkgs, moonarchThemes, ... }:
let
xdg = "${moonarchSrc}/defaults/xdg";
# waypaper calls `swww`/`swww-daemon`, but the maintained successor ships as
# `awww`. The symlinks keep the shared config working unchanged.
swww-compat = pkgs.runCommand "swww-compat" { } ''
mkdir -p "$out/bin"
ln -s ${pkgs.awww}/bin/awww "$out/bin/swww"
ln -s ${pkgs.awww}/bin/awww-daemon "$out/bin/swww-daemon"
'';
wallpaper = "${moonarchSrc}/defaults/backgrounds/wallpaper.jpg";
# The FHS path the wallpaper is published under, provided by the tmpfiles
# link further down. waypaper's config names /usr/share/moonarch/wallpaper.jpg
# and cannot keep a store path: waypaper reads only the copy in the home
# directory, which is seeded once and never rewritten, so a store path baked
# into it survives exactly until the next moonarch bump collects the old
# source — and then the session comes up black. A path that activation
# relinks holds across every bump.
wallpaperPath = "/usr/share/backgrounds/moonarch.jpg";
waypaperConfig = pkgs.runCommand "waypaper-config.ini" { } ''
substitute ${xdg}/waypaper/config.ini "$out" \
--replace-fail /usr/share/moonarch/wallpaper.jpg ${wallpaperPath}
'';
# 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"
'';
in
{
# --- XDG configs -> /etc/xdg/ ---
#
# Deployed verbatim from the moonarch repo; defaults/ stays the single
# source of truth for both distros.
#
# defaults/xdg/walker/ is not deployed: the Quickshell launcher covers it.
environment.etc = {
# niri reads /etc/niri, not /etc/xdg/niri. Both are provided: the former
# is what niri actually loads, the latter keeps the layout consistent with
# moonarch's tree and with any user config that includes it.
"niri/config.kdl".source = "${xdg}/niri/config.kdl";
"xdg/niri/config.kdl".source = "${xdg}/niri/config.kdl";
"xdg/quickshell/moonarch".source = "${xdg}/quickshell/moonarch";
"xdg/foot/foot.ini".source = "${xdg}/foot/foot.ini";
# No xdg/pipewire/ entry: PipeWire does not read /etc/xdg, so the file was
# deployed and inert. It goes through services.pipewire.configPackages
# in modules/audio.nix instead.
# Kept for layout parity with moonarch's tree; waypaper itself only reads
# the copy in the home directory, seeded below.
"xdg/waypaper/config.ini".source = waypaperConfig;
# No stasis entry: /etc/stasis/ is only its fallback for a missing user
# config, and the seed below means there never is one.
# No xdg-desktop-portal entry: moonarch's niri-portals.conf would be
# deployed to /etc/xdg-desktop-portal/, and portals.conf(5) searches that
# path — the build-time sysconfdir — only after every directory in
# $XDG_CONFIG_DIRS. /etc/xdg is one of those, and it is where NixOS writes
# the same file from xdg.portal.config.niri, which programs.niri defines.
# The deployed copy would rank below it and decide nothing. What it carries
# is set under Portals below instead.
};
# --- Compositor ---
programs.niri.enable = true;
# niri searches $XDG_CONFIG_HOME/niri/config.kdl, then /etc/niri/config.kdl —
# not /etc/xdg. The system path is used directly, so nothing has to be placed
# in the home directory to bridge there.
# config.kdl spawns the polkit agent by its FHS path
# (/usr/lib/polkit-gnome/...). Provide that path rather than diverging the
# config, which moonarch owns.
systemd.tmpfiles.rules = [
"d /usr/lib 0755 root root -"
"L+ /usr/lib/polkit-gnome - - - - ${pkgs.polkit_gnome}/libexec"
# The wallpaper under its FHS name, for the reason given at wallpaperPath.
# 'L+' replaces the link on every activation, so it always names the
# current moonarch source and the seeded waypaper config never goes stale.
"d /usr/share 0755 root root -"
"d /usr/share/backgrounds 0755 root root -"
"L+ ${wallpaperPath} - - - - ${wallpaper}"
];
# --- Portals ---
#
# The whole assignment lives here, in the one file NixOS writes. programs.niri
# defines xdg.portal.config.niri with default=gnome;gtk and Access,
# FileChooser, Notification and Secret — the same four moonarch's
# niri-portals.conf carries, which is why that file is not deployed; see the
# note in environment.etc above. Only what moonarch states beyond them is
# restated below.
xdg.portal = {
enable = true;
extraPortals = with pkgs; [ xdg-desktop-portal-gnome xdg-desktop-portal-gtk ];
# Carried over from moonarch's file verbatim, its reasoning included:
# xdg-desktop-portal-gtk reports the Inhibit interface as success even
# though nothing implements it under niri, which makes Firefox skip the
# native Wayland idle-inhibit. With no backend the browser falls back to
# zwp_idle_inhibit, which niri honors, so windowed video keeps the screen
# awake. programs.niri leaves this interface unassigned, so stating it here
# does not collide with the module's definition.
config.niri."org.freedesktop.impl.portal.Inhibit" = "none";
};
# The file dialog every portal client opens — Firefox's save dialog among
# them. The option defaults to true, which registers nautilus with D-Bus and
# leaves org.freedesktop.impl.portal.FileChooser unassigned, so the
# default=gnome;gtk line hands it to xdg-desktop-portal-gnome and the dialog
# is nautilus' own: GTK4 and libadwaita, which draw their own style and never
# read gtk-theme-name out of settings.ini. That is why the dialog came up in
# Adwaita while thunar, a GTK3 program reading /etc/xdg/gtk-3.0/settings.ini,
# was themed.
#
# Off, the module assigns the interface to gtk and drops the nautilus D-Bus
# registration; xdg-desktop-portal-gtk then builds the dialog with GTK3 from
# that same settings.ini. It is set through the option rather than through
# config.niri above because only the option covers the D-Bus half — and
# setting the key in both places would collide. Screencasting is unaffected:
# niri serves org.gnome.Mutter.ScreenCast itself and only the gnome backend
# calls it, which is why that one stays in extraPortals.
programs.niri.useNautilus = false;
environment.systemPackages = with pkgs; [
# XDG plumbing, before anything that calls into it. xdg-terminal-exec
# resolves a caller to whichever TerminalEmulator entry the XDG data
# hierarchy offers — footclient here, which reuses the server niri starts
# — so nothing downstream has to name an emulator.
xdg-terminal-exec
# Own programs
moonarchPkgs.moonarch-scripts
xwayland-satellite
# Bar, notifications, clipboard.
# Neither waybar nor swaync are installed: the Quickshell bar owns both
# roles. The "kept as a reserve" notes in moonarch's config date from the
# migration and do not apply to a fresh setup.
quickshell
libnotify
cliphist
wl-clipboard
# The launcher's start prefix, and why the launcher needs one: Quickshell
# spawns applications as its own children, and a child inherits its
# parent's cgroup, so everything started from the launcher lands inside
# quickshell.service — which systemd kills whole on every restart of the
# unit (KillMode=control-group is the default). A crash of the bar took the
# open browser with it, which counted each one as a crash until it stopped
# restoring the session at all. app2unit wraps systemd-run to put
# each application in its own transient app-*.scope, the same shape niri
# gives the windows it spawns itself.
app2unit
# Display and wallpaper
wlsunset
brightnessctl
awww
swww-compat
waypaper
# Terminal
foot
# Media
vlc
viewnior
# System
upower
polkit_gnome
nwg-displays
wl-color-picker
stasis
claude-code
galculator
];
# Polkit agent for privilege prompts inside the session.
security.polkit.enable = true;
# The setuid pkexec wrapper is opt-in and off by default. Without it pkexec
# refuses to run at all, which takes the camera and battery toggles with it —
# both reach their privileged half through pkexec.
security.polkit.enablePkexecWrapper = true;
# nixpkgs.config.allowUnfree covers the system build only. `nix profile` and
# friends evaluate a flake purely, so they see neither that setting nor
# ~/.config/nixpkgs/config.nix, and refuse every unfree package. The hosts
# already declare unfree as acceptable; this makes the imperative commands
# agree instead of demanding NIXPKGS_ALLOW_UNFREE=1 in front of each one.
# Set by PAM at session start, so it takes a re-login, not just a rebuild.
environment.sessionVariables.NIXPKGS_ALLOW_UNFREE = "1";
# Seed configs for programs that read only from the home directory and have
# no system-wide fallback. 'C' copies once and never overwrites, so user
# edits survive. This is what moonarch's post-install.sh does imperatively.
#
# No Kvantum entry: the theme is a package now, found through XDG_DATA_DIRS.
# The rule it replaces never worked in a user unit anyway — 'C' recreates the
# destination with the source's mode, and a store directory is r-xr-xr-x, so
# copying into it failed silently for anyone but root and left the directory
# empty (systemd 261, reproduced).
systemd.user.tmpfiles.rules = [
"C %h/.config/waypaper/config.ini 0644 - - - ${waypaperConfig}"
# stasis prefers the user config over /etc and generates its own if none
# exists, so the moonarch config has to be in place before it starts.
"C %h/.config/stasis/stasis.rune 0644 - - - ${xdg}/stasis/stasis.rune"
# No gtklock entry: it reads /etc/xdg, which swaylock could not.
# 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}"
];
}