feat(theme): a splash theme of our own, images in viewnior, a stop timeout for stasis

The Catppuccin splash showed the NixOS logo and the loading animation and
nothing else. The NixOS plymouth module links boot.plymouth.logo into every
theme directory named catppuccin-<flavour>, and the two-step module has no
alignment key for that header image, so its distance from the animation is not
the theme's to set. A theme under a name of its own is outside that loop.
moonarch-plymouth joins as a flake input; pkgs/moonarch-plymouth.nix rewrites
the descriptor's FHS ImageDir to the store path.

Images opened in Chromium although viewnior has been installed since 3e40a70:
no mimeapps.list named image/* at all, so xdg-open took the first candidate
from mimeinfo.cache. xdg.mime points gif, jpeg, png and webp at
viewnior.desktop. webp needs addedAssociations besides, which viewnior.desktop
does not declare, and webp-pixbuf-loader, without which gdk-pixbuf cannot
decode it.

stasis does not answer SIGTERM as part of the session teardown, and the default
90 seconds left the whole user manager waiting on it: niri received its own
SIGTERM only once stasis had been killed, 88 seconds in the journal.
TimeoutStopSec = 5. What it holds is an idle timer, so nothing is lost when it
goes down hard.

glab on the ThinkPad.
This commit is contained in:
nevaforget committed 2026-09-28 14:52:05 +02:00
1 parent de70d1794e
commit dddf236ace
9 files changed
+190 -7

No files matched your search

+102
View File
@@ -1,5 +1,107 @@
# Decisions
## 2026-09-28 – Images open in the viewer, not in the browser
- **Who**: Dominik, ClaudeCode
- **Why**: Images opened in Chromium although `viewnior` has been installed
since `3e40a70`. No `mimeapps.list` named `image/*` at all, so `xdg-open` took
the first candidate from `mimeinfo.cache` — `chromium-browser.desktop`, ahead
of `viewnior.desktop` by name.
- **Tradeoffs**: Declared in `modules/desktop.nix` next to the viewer, not
beside the Firefox types in `modules/browsers.nix`; both modules add to the
same attribute set and the keys do not overlap.
- **How**: `xdg.mime.defaultApplications` for gif, jpeg, png and webp on
`viewnior.desktop`, plus `addedAssociations` for webp, which
`viewnior.desktop` does not declare, and
`programs.gdk-pixbuf.modulePackages = [ pkgs.webp-pixbuf-loader ]`, without
which gdk-pixbuf cannot decode it.
## 2026-09-28 – The splash in our own colours, and what Plymouth would not give
- **Who**: Dominik, ClaudeCode
- **Why**: The first boot on the new theme showed four things: the keyboard
layout label at the password prompt was too large, it should sit further
down, the entry field was pixelated, and the palette was the upstream blue
rather than the Catppuccin lavender the rest of the system runs on.
- **Tradeoffs**: Three of the four are files the theme ships, so they are
drawn here and committed — not generated in the package recipe. That keeps
the recipe untouched and puts every asset where it can be looked at. The
cost is that `keymap-render.png` now has to match Plymouth's offset table
by hand; the README says so, and the band from the original upload had
failed exactly that way.
The fourth is not a file. Plymouth places the layout indicator at
`y = dialog_bottom + …` (`two-step/plugin.c`) and has no alignment key for
it, so it cannot be moved without moving the entry field with it. Making
the images taller and putting their content at the bottom would work in
principle, but reaching the screen's lower edge from the middle needs over
a thousand pixels of empty image. Dropped.
- **How**: `keymap-render.png` redrawn at 2304×24 — one layout, `de`, at the
offset the table gives it (2253, 51 wide) — against 26537×50 upstream;
`keyboard.png` scaled to match. `entry.png` redrawn at its original
305×75 with smooth edges. Both frame series, 81 `animation-*` and 51
`progress-*`, moved to lavender with `-modulate 130,125,105`, which lands
on `#b0c1ee` against the target `#b4befe`. Background and progress bar in
the descriptor follow `#1e1e2e`, `#313244`, `#b4befe`.
## 2026-09-28 – A stop timeout for stasis
- **Who**: Dominik, ClaudeCode
- **Why**: Every reboot hung for a minute and a half. The journal names it:
`stasis.service: State 'stop-sigterm' timed out. Killing.`, 88 seconds after
the last system service went down, and niri received its own SIGTERM only
once stasis had been killed — the whole user manager was waiting on that one
unit. Stopped by hand with `systemctl --user stop stasis` it exits in zero
seconds, so the unit is not at fault; as part of the session teardown it
does not answer.
- **Tradeoffs**: The cause sits in the program. stasis registers SIGTERM
through `signal_hook` and honours it in normal operation, so fixing it means
changing somebody else's Rust daemon and carrying a patch. A short stop
timeout treats the symptom instead — acceptable here because what stasis
holds is an idle timer, and nothing is lost when it goes down hard. The
alternative, leaving the default 90 seconds, costs that time on every
reboot.
- **How**: `TimeoutStopSec = 5` in the unit in `modules/services.nix`, with
the measurement recorded beside it so the number is not mistaken for a
guess.
## 2026-09-28 – A splash theme of our own, and a flake input after all
- **Who**: Dominik, ClaudeCode
- **Why**: The Catppuccin splash showed the NixOS logo and the loading
animation and nothing else, the two of them touching. The cause is not the
theme's configuration: `nixos/modules/system/boot/plymouth.nix` links
`boot.plymouth.logo` into every theme directory named `catppuccin-<flavour>`
as `header-image.png`, and the `two-step` module that theme runs on has
alignment keys for `watermark`, `title`, `dialog` and the progress bar —
none for the header image, whose distance from the animation the module
decides on its own. The theme itself contributes only the throbber frames:
it sets `WatermarkVerticalAlignment` but ships no `watermark.png`, so those
lines take no effect.
- **Tradeoffs**: (1) **A padded logo** — the same PNG with transparent space
below it. One line, but the spacing then lives inside an image file and
imagemagick joins the build. (2) **A derived theme under another name**,
with the logo going in as `watermark.png`. Uses the slot Plymouth intends
for a logo, but builds a package only to sidestep a loop over theme names.
(3) **Patching that loop in nixpkgs**, where the cause sits, which needs an
overridden module and a changed theme value on top. (4) **A theme of our
own**, which removes the question entirely: the loop matches only Catppuccin
and Spinner names, so every other theme is left alone. Chosen.
This reverses the reasoning of the 2026-09-25 entry below, which took
nixpkgs' `catppuccin-plymouth` precisely to avoid a new input. The theme
wanted here is not in nixpkgs, and carrying it as a source repo is the same
shape the flake already uses for Betterfox and the cursors.
- **How**: `gitea.moonarch.de/nevaforget/moonarch-plymouth-theme`, derived
from tobilinuxer's *Linux Distro Logos - macOS Boot Style*
(opendesktop.org/p/2106821) under the X11 license, which permits the change;
the copyright notice travels with it in `LICENSE`. Renamed to `moonarch` in
the descriptor's filename, its `Name=` entry and its `ImageDir`, the images
untouched. It runs on `two-step` as well, but brings a progress bar and a
138-frame animation and no watermark, so nothing is drawn that it did not
bring itself. `pkgs/moonarch-plymouth.nix` rewrites the descriptor's FHS
`ImageDir` to the store path, the way nixpkgs' own theme packages do.
## 2026-09-25 – amdgpu back in the ThinkPad's initrd, for Plymouth's sake
- **Who**: Dominik, ClaudeCode
+1
View File
@@ -20,6 +20,7 @@ files therefore serves every system that pulls it in.
flake.nix Inputs and outputs
pkgs/ Build recipes
sweet-cursors.nix cursor theme
moonarch-plymouth.nix boot splash theme
moonarch-scripts.nix battery and camera helpers
betterfox.nix Firefox preference set
modules/
Generated
+17
View File
@@ -53,6 +53,22 @@
"url": "https://gitea.moonarch.de/nevaforget/moonarch.git"
}
},
"moonarch-plymouth": {
"flake": false,
"locked": {
"lastModified": 1790599371,
"narHash": "sha256-4eudA/FgaCTVO+dluJcTfEBsPFOiEThKu0V4UA8YPjw=",
"ref": "refs/heads/main",
"rev": "5307ddd8056e8403355bf9b3d68b6fbd2c57d718",
"revCount": 9,
"type": "git",
"url": "https://gitea.moonarch.de/nevaforget/moonarch-plymouth-theme.git"
},
"original": {
"type": "git",
"url": "https://gitea.moonarch.de/nevaforget/moonarch-plymouth-theme.git"
}
},
"nixos-hardware": {
"inputs": {
"nixpkgs": "nixpkgs"
@@ -105,6 +121,7 @@
"betterfox": "betterfox",
"disko": "disko",
"moonarch": "moonarch",
"moonarch-plymouth": "moonarch-plymouth",
"nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_2",
"sweet-cursors": "sweet-cursors"
+9
View File
@@ -32,6 +32,10 @@
url = "git+https://gitea.moonarch.de/nevaforget/Sweet-cursors.git";
flake = false;
};
moonarch-plymouth = {
url = "git+https://gitea.moonarch.de/nevaforget/moonarch-plymouth-theme.git";
flake = false;
};
};
outputs = { self, nixpkgs, ... }@inputs:
@@ -50,6 +54,11 @@
version = "0-unstable";
};
moonarch-plymouth = pkgs.callPackage ./pkgs/moonarch-plymouth.nix {
src = inputs.moonarch-plymouth;
version = "0-unstable";
};
moonarch-scripts = pkgs.callPackage ./pkgs/moonarch-scripts.nix {
src = inputs.moonarch;
};
+1
View File
@@ -103,6 +103,7 @@
pnpm
uv
python3
glab
];
# The nixos-hardware profile only sets hardware.cpu.amd.updateMicrocode as a
+12
View File
@@ -193,6 +193,18 @@ in
galculator
];
xdg.mime = {
defaultApplications = {
"image/gif" = "viewnior.desktop";
"image/jpeg" = "viewnior.desktop";
"image/png" = "viewnior.desktop";
"image/webp" = "viewnior.desktop";
};
addedAssociations."image/webp" = "viewnior.desktop";
};
programs.gdk-pixbuf.modulePackages = [ pkgs.webp-pixbuf-loader ];
# 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
+6
View File
@@ -272,6 +272,12 @@ in
ExecReload = "${lib.getExe pkgs.stasis} reload";
Restart = "on-failure";
RestartSec = 2;
# Stopped by hand it exits at once; stopped as part of the session it
# does not answer SIGTERM, and the default 90 seconds left the whole
# user manager waiting on it — niri received its own SIGTERM only once
# stasis had been killed. Measured at 88s in the journal. What it holds
# is an idle timer, so nothing is lost when it goes down hard.
TimeoutStopSec = 5;
IPAddressDeny = "any";
ProtectProc = "invisible";
ProcSubset = "pid";
+5 -7
View File
@@ -47,11 +47,6 @@ let
# it, so the link target below cannot drift from what Kvantum looks up.
kvantumThemeName = "catppuccin-mocha-lavender";
# Mocha like everything else here. The package installs under
# share/plymouth/themes/catppuccin-${variant}, which is the name boot.plymouth
# asks for. Upstream Catppuccin has no accent variants for Plymouth.
plymouthVariant = "mocha";
plymouthTheme = pkgs.catppuccin-plymouth.override { variant = plymouthVariant; };
in
{
environment.etc = {
@@ -119,10 +114,13 @@ in
# With systemd in the initrd, which this configuration uses, systemd asks for
# the LUKS passphrase and Plymouth draws that prompt; the script initrd instead
# shows it as text before the splash comes up.
# The theme carries a name of its own, outside the catppuccin-<flavour> set
# that the NixOS plymouth module links its logo into, so the splash shows
# what the theme brings and nothing besides.
boot.plymouth = {
enable = true;
theme = "catppuccin-${plymouthVariant}";
themePackages = [ plymouthTheme ];
theme = "moonarch";
themePackages = [ moonarchPkgs.moonarch-plymouth ];
};
# Qt applications are wrapped, so each one looks for plugins in its own
+37
View File
@@ -0,0 +1,37 @@
# ABOUTME: Nix build recipe for the Moonarch Plymouth boot splash.
# ABOUTME: Rewrites the descriptor's FHS ImageDir to wherever the theme lands.
{ lib, stdenvNoCC, src, version }:
stdenvNoCC.mkDerivation {
pname = "moonarch-plymouth";
inherit version src;
dontBuild = true;
dontConfigure = true;
# The theme's own name, which boot.plymouth.theme has to match. It stays
# outside the catppuccin-<flavour> set that the NixOS plymouth module links
# its logo into, so nothing is added to the splash that the theme did not
# bring itself.
installPhase = ''
runHook preInstall
dir="$out/share/plymouth/themes/moonarch"
install -dm755 "$dir"
cp -r images "$dir/images"
# ImageDir in the repo names an FHS path; plymouthd reads it verbatim.
substitute moonarch.plymouth "$dir/moonarch.plymouth" \
--replace-fail /usr/share/plymouth/themes/moonarch "$dir"
runHook postInstall
'';
meta = {
description = "Moonarch Plymouth boot splash";
homepage = "https://gitea.moonarch.de/nevaforget/moonarch-plymouth-theme";
license = lib.licenses.x11;
platforms = lib.platforms.linux;
};
}