greetd-moongreet/DECISIONS.md
nevaforget ce9f2196ca
All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 2s
refactor: single greeter window, drop per-output keyboard grab (v0.10.0)
The v0.8.0 → v0.8.5 multi-monitor story (login widget on primary only,
wallpaper-only on secondaries, KeyboardMode::Exclusive on the primary
surface, hotplug handler) was a pile of workarounds that kept breaking
on real hardware — after switching the greeter compositor to niri, the
pointer could not cross onto the primary output and keyboard tab did
not reach the UI. Niri in a normal user session has none of these
issues; the bug was moongreet's own output-scoped policy.

Back to basics: one layer-shell window on the built-in display,
KeyboardMode::OnDemand. Secondary outputs stay under compositor
control. No hotplug callbacks, no wallpaper-only windows, no DisplayLink
phantom workarounds.

-81 / +26 lines.
2026-04-24 11:27:48 +02:00

16 KiB
Raw Blame History

Decisions

2026-04-24 Single greeter window, no per-output keyboard grab (v0.10.0)

  • Who: ClaudeCode, Dom
  • Why: The v0.8.0 → v0.8.4 → v0.8.5 sequence accumulated multi-monitor logic (login widget on primary, wallpaper-only on secondaries, KeyboardMode::Exclusive on the primary surface) to work around keyboard-routing on specific hardware setups. After the moonarch greeter switched to niri, the symptoms returned: the UI was on eDP-1 but the pointer could not cross onto it, and keyboard tab did not reach the login widget. Niri in a normal user session never behaves like this — the issue was our greeter's self-imposed per-output scope, not the compositor. Every earlier "fix" made it more bespoke instead of making moongreet a well-behaved layer-shell client.
  • Tradeoffs: Reverts the multi-output story entirely. Secondary monitors get nothing from moongreet — the compositor decides what renders there (black, its own wallpaper, whatever). The "wallpaper on every screen" look is gone. In exchange, cursor and keyboard follow normal niri focus rules, nothing is grabbed, no hotplug callbacks, no DisplayLink phantom workarounds.
  • How: main.rs::activate builds one greeter window, anchors it to the built-in display picked by pick_primary_monitor_index, and calls setup_layer_shell with KeyboardMode::OnDemand. The hotplug connect_items_changed handler is gone. create_wallpaper_window is removed. setup_layer_shell no longer takes a keyboard: bool — there is only one policy.

2026-04-24 Cursor theme via config instead of env (v0.9.0)

  • Who: ClaudeCode, Dom
  • Why: Cursor theme in the greeter was the default fallback even with XCURSOR_THEME=Sweet-cursors in /etc/greetd/config.toml's env prefix. Cage forwards the env, but GTK4 does not honour XCURSOR_THEME reliably under greetd — it picks up the theme from gtk-cursor-theme-name on GtkSettings, and without a session-level settings.ini or GSettings override in the greeter user's home, that property stays at the GTK default. Adding an env-var hack worked for the wlroots pointer rendered by cage, but GTK widgets (button hover, text input) used their own wrong cursor.
  • Tradeoffs: Adds two config fields (cursor-theme, cursor-size) — symmetric with the existing gtk-theme field and justified by the same cause (GTK4 under greetd ignores the usual discovery paths). Alternative would have been a system-wide /etc/gtk-4.0/settings.ini with gtk-cursor-theme-name=, but that couples moongreet's appearance to the host system's GTK config and affects every GTK4 app running as any user.
  • How: config.rs gains cursor_theme: Option<String> and cursor_size: Option<i32> (range-validated 1256). greeter.rs::create_greeter_window applies them via gtk::Settings::set_gtk_cursor_theme_name() and set_gtk_cursor_theme_size() directly after the existing gtk-theme handling, reusing is_valid_gtk_theme() for name validation. Moonarch's deployed config gains cursor-theme = "Sweet-cursors" + cursor-size = 24. The env-prefix hack in /etc/greetd/config.toml is now redundant.

2026-04-23 Wallpaper-only windows on secondary monitors (v0.8.5)

  • Who: ClaudeCode, Dom
  • Why: The v0.8.4 fix (keyboard grab on the built-in panel) only half-worked. The greeter still rejected keystrokes until the user moved the mouse to eDP-1 — Niri scopes layer-shell keyboard routing by active output, so even though the primary window was the sole KeyboardMode::Exclusive surface, keys went nowhere when another output was active. Hardcoding a compositor focus call (e.g. niri msg action focus-monitor) would tie moongreet to a specific compositor.
  • Tradeoffs: Reverts part of 2026-04-08: only the built-in panel shows the full greeter UI, other monitors go back to wallpaper-only. Users with multiple monitors lose the symmetric "login widget on every screen" look, but gain a reliable keyboard path regardless of which output the compositor considers active at startup. Compositor-agnostic — no Niri-specific IPC.
  • How: New create_wallpaper_window() in greeter.rs builds a minimal ApplicationWindow with the shared background Picture (same blur_cache as the primary) and no login widgets. main.rs uses create_greeter_window() for the index returned by pick_primary_monitor_index() and create_wallpaper_window() for the rest. Hotplugged monitors also get wallpaper-only windows. Both variants use Layer::Top; only the primary sets KeyboardMode::Exclusive.

2026-04-23 Keyboard focus on built-in display, not first enumerated monitor (v0.8.4)

  • Who: ClaudeCode, Dom
  • Why: With a DisplayLink dock attached, the greeter showed its UI on all monitors but the password entry accepted no input. display.monitors() enumerated evdi phantom connectors (DVI-I-*) before the laptop panel (eDP-1); the v0.8.0 logic gave KeyboardMode::Exclusive to index 0, so the keyboard grab landed on an invisible surface. Symptom showed up on 2026-04-23 after kernel 6.19.11 → 6.19.12 + moongreet 0.8.0 → 0.8.2 changed evdi enumeration timing — previous Thursdays with the same dock worked.
  • Tradeoffs: Prefers built-in displays by connector-name pattern (eDP*/LVDS*/DSI*) rather than a generic "primary monitor" concept — Wayland has no portable primary signal, and gdk4's primary_monitor() was removed. Pattern-matching covers every current Linux laptop, at the cost of a tiny list to maintain if a new form factor ships a new connector type. Fallback is still index 0, so behavior on desktops without a built-in panel is unchanged.
  • How: New pure function pick_primary_monitor_index() in main.rs scans connector names and returns the built-in index (or 0). Used during initial enumeration to decide which window gets KeyboardMode::Exclusive. Hotplug branch unchanged — new monitors still get keyboard=false so focus never migrates off the panel. Unit-tested against evdi/eDP/LVDS/DSI/HDMI/DP mixes.

2026-04-21 Ship polkit rule in moongreet instead of moonarch (v0.8.3)

  • Who: ClaudeCode, Dom
  • Why: Reboot/shutdown from the greeter silently failed on a fresh install. The polkit rule that grants the greeter user org.freedesktop.login1.{reboot,power-off} lived in the moonarch repo but was never installed by any PKGBUILD. The laptop worked only because the rule had been hand-deployed once.
  • Tradeoffs: Rule ownership moves from moonarch (system defaults) to moongreet (greeter-specific auth). Cleaner boundary — moonarch no longer needs to know about the greeter's auth requirements — but it means moongreet is now responsible for a system polkit rule that ties it to a fixed username (greeter).
  • How: Source file moved to moongreet/config/polkit/50-moongreet-power.rules, installed to /etc/polkit-1/rules.d/ by moongreet-git/PKGBUILD. Old file removed from the moonarch repo.

2026-04-09 Monitor hotplug via ListModel items-changed

  • Who: ClaudeCode, Dom
  • Why: Greeter windows were only created at startup. If a monitor was hotplugged (e.g. HDMI reconnect), it would show no greeter UI. Aligned with moonlock's hotplug fix (same day).
  • Tradeoffs: Hotplugged monitors get greeter windows without keyboard input (keyboard stays on the primary monitor). Acceptable — user can still interact on the primary screen.
  • How: Connect to display.monitors().connect_items_changed() and create new greeter windows for added monitors. Shared state (config, texture, blur_cache) moved to Rc for the closure.

2026-04-08 Show greeter UI on all monitors instead of just one

  • Who: ClaudeCode, Dom
  • Why: moonlock showed its UI on all monitors via ext-session-lock-v1, but moongreet only showed the login UI on one monitor (compositor-picked) with wallpaper-only windows on the rest. Inconsistent UX across the ecosystem.
  • Tradeoffs: Each monitor gets its own full greeter widget tree (slightly more memory), but the UI is lightweight. Screen mirroring (e.g., wl-mirror/screencopy) was considered and rejected — it requires an external process, compositor screencopy support, adds latency, and fights Wayland's per-output model. One-window-per-monitor is the established Wayland pattern (swaylock, hyprlock, moonlock all do this).
  • How: Create one create_greeter_window() per monitor with set_monitor(), only the first gets KeyboardMode::Exclusive. Removed create_wallpaper_window() (no longer needed). No layer shell fallback keeps single-window mode for development.

2026-04-06 Restore explicit gtk-theme in moongreet config

  • Who: ClaudeCode, Dom
  • Why: GTK4 under greetd does not reliably read /etc/xdg/gtk-4.0/settings.ini — likely requires a settings daemon that doesn't run in the greeter session. moongreet fell back to Adwaita/Colloid-default (blue accent) instead of Colloid-Grey-Dark-Catppuccin.
  • Tradeoffs: Reverts 094878f ("Remove gtk-theme from app config, use system-wide GTK settings instead"). Duplicates the theme name between settings.ini and moongreet.toml, but the explicit set via set_gtk_theme_name() is the only reliable path in a greetd context.
  • How: Added gtk-theme = "Colloid-Grey-Dark-Catppuccin" to example config and deployed /etc/moongreet/moongreet.toml.

2026-04-02 Replace hardcoded CSS colors with GTK theme variables

  • Who: ClaudeCode, Dom
  • Why: moongreet used hardcoded colors (#1a1a2e, white, #ff6b6b) while moonset already used @theme_bg_color, @theme_fg_color, @error_color etc. Inconsistent across the ecosystem and broke theme flexibility.
  • Tradeoffs: Depends on the active GTK theme defining standard color variables. Catppuccin Colloid provides all needed vars (@theme_bg_color, @theme_fg_color, @error_color, @success_color, @theme_selected_bg_color). Fallback behavior if a theme lacks vars is GTK's default colors — acceptable.
  • How: Replaced all hardcoded hex/named colors with GTK theme variables. Coordinated change across moongreet, moonlock, and moonset (all three now use identical pattern).

2026-03-31 Fourth audit: power timeout, timing mitigation, release profile, GREETD_SOCK caching

  • Who: ClaudeCode, Dom
  • Why: Fourth triple audit found moongreet power.rs had no timeout on loginctl (greeter could freeze), username enumeration via timing differential, GREETD_SOCK re-read on every login, missing release profile, and missing GResource compression.
  • Tradeoffs: 500ms minimum login response time adds slight delay on fast auth but prevents timing-based username enumeration. Power timeout (30s + SIGKILL) matches moonset pattern — aggressive but prevents greeter freeze.
  • How: (1) power.rs adapted from moonset with 30s timeout + SIGKILL (nix dependency added). (2) 500ms min response floor in attempt_login via Instant + glib::timeout_future. (3) GREETD_SOCK cached in GreeterState at startup. (4) [profile.release] with LTO, codegen-units=1, strip. (5) compressed="true" on GResource entries. (6) SYNC comments on duplicated blur/background functions.

2026-03-30 Full audit fix: security, quality, performance (v0.6.2)

  • Who: ClaudeCode, Dom
  • Why: Three parallel audits (security, code quality, performance) identified 10 actionable findings across the codebase — from world-readable cache dirs to a GPU blur geometry bug to a race condition in fingerprint probing.
  • Tradeoffs: too_many_arguments Clippy warnings suppressed with #[allow] rather than introducing a UiWidgets struct — GTK's clone! macro with #[weak] refs requires individual widget parameters, a struct would fight the idiom. Async avatar loading skipped because Pixbuf is !Send; cache already prevents repeat loads. TOCTOU socket pre-check removed entirely — connect() in login_worker already handles errors, the metadata() check gave false security guarantees.
  • How: Cache dirs use DirBuilder::mode(0o700) instead of create_dir_all. Blur config clamped to 0.0..=200.0 with is_finite() guard. Blur texture cached in Rc<RefCell<Option<gdk::Texture>>> across monitors. FingerprintProbe device proxy cached in GreeterState with generation counter to prevent stale async writes. GPU blur geometry fixed (-pad origin shift instead of texture stretching). is_valid_gtk_theme extracted as testable function. 9 new tests.

2026-03-29 Fingerprint authentication via greetd multi-stage PAM

  • Who: ClaudeCode, Dom
  • Why: moonlock supports fprintd but moongreet rejected multi-stage auth. Users with enrolled fingerprints couldn't use them at the login screen.
  • Tradeoffs: Direct fprintd D-Bus verification (like moonlock) can't start a greetd session — greetd controls session creation via PAM. Using greetd multi-stage means PAM decides the auth order (fingerprint first, then password fallback), not truly parallel. Acceptable — matches standard pam_fprintd behavior.
  • How: Replace single-pass auth with a loop over auth_message rounds. Secret prompts get the password, non-secret prompts (fprintd) get None and block until PAM resolves. fprintd D-Bus probe (gio::DBusProxy) only for UI — detecting device availability and enrolled fingers. 60s socket timeout when fingerprint available. Config option fingerprint-enabled (default true).

2026-03-28 Remove embedded wallpaper from binary

  • Who: ClaudeCode, Dom
  • Why: Wallpaper is installed by moonarch to /usr/share/moonarch/wallpaper.jpg. Embedding a 374K JPEG in the binary is redundant. GTK background color (Catppuccin Mocha base) is a clean fallback.
  • Tradeoffs: Without moonarch installed AND without config, greeter shows plain dark background instead of wallpaper. Acceptable — that's the expected minimal state.
  • How: Remove wallpaper.jpg from GResources, return None from resolve_background_path when no file found, skip wallpaper window creation and background picture when no path available.

2026-03-28 GPU blur via GskBlurNode replaces CPU blur

  • Who: ClaudeCode, Dom
  • Why: CPU-side Gaussian blur (image crate) blocked the GTK main thread for 500ms2s on 4K wallpapers at cold cache. Disk cache and async orchestration added significant complexity.
  • Tradeoffs: GPU blur quality is slightly different (box-blur approximation vs true Gaussian), acceptable for wallpaper backgrounds. Removes image crate dependency entirely (~15 transitive crates eliminated). No disk cache needed.
  • How: Snapshot::push_blur() + GskRenderer::render_texture() on connect_realize. Blur happens once on the GPU when the widget gets its renderer, producing a concrete gdk::Texture. Zero startup latency. Symmetric with moonlock and moonset.

2026-03-28 Optional background blur via image crate (superseded)

  • Who: ClaudeCode, Dom
  • Why: Blurred wallpaper as greeter background is a common UX pattern for login screens
  • Tradeoffs: Adds image crate dependency (~15 transitive crates); CPU-side Gaussian blur at load time adds startup latency proportional to image size and sigma. Acceptable because blur runs once and the texture is shared across monitors.
  • How: load_background_texture(bg_path, blur_radius) loads texture, optionally applies imageops::blur(), returns blurred gdk::Texture. Config option background-blur: Option<f32> in [appearance] TOML section.

2026-03-28 Audit fixes for shared wallpaper texture (v0.4.1)

  • Who: ClaudeCode, Dominik
  • Why: Quality, performance, and security audits flagged issues in load_background_texture(), debug logging, and greetd error handling
  • Tradeoffs: GResource path now requires UTF-8 (returns None for non-UTF-8 instead of aborting); 50 MB wallpaper limit is generous but prevents OOM; debug logging off by default trades observability for security
  • How: GResource branch via resources_lookup_data() + from_bytes() (no abort), file size limit, error details only at debug level, MOONGREET_DEBUG env var for log level, greetd retry path truncation matching show_greetd_error()