Desktop

Desktop Environment Architecture

A multi-desktop strategy designed for stability, high-performance, and seamless recovery.

🏗️ The Multi-Desktop Strategy

This repository supports having multiple desktop environments (Compositors/WMs) installed simultaneously while maintaining a single “Primary Session.” This allows for advanced debugging and development without risking system lockouts.

1. Enable vs. Session (The Dual-Toggle System)

Each desktop module (Hyprland, Niri, BSPWM) can be installed by enabling it.

  • modules.desktop.<wm>.enable:
    • Action: Installs the compositor binary and links dotfiles (e.g., ~/.config/hypr).

2. Centralized Shell Architecture

To ensure a consistent user experience and reduce configuration duplication, the shell environment is decoupled from the window manager. This is managed via the global modules.desktop.mode option.

  • dms (Dank Material Shell): A modern, integrated shell experience managed by modules/desktop/shell/dms.nix. It provides a unified status bar, notification system, and greeter integration.
  • diy (Do It Yourself): A modular, power-user environment managed by modules/desktop/shell/diy.nix. It defaults to tools like Waybar (Wayland) or Polybar (X11), and Mako or Dunst for notifications.

Each window manager (Hyprland, Niri, BSPWM) focuses purely on compositor-specific logic while automatically inheriting its shell environment based on this global mode.


🔐 Unified Greeter & Display Management

We use a centralized greeter service (modules/services/desktop/greetd.nix) to manage system access.

Supported Greeters

  • DMS Greeter: The default for dms mode, providing a modern Wayland-native login experience.
  • tuigreet: A universal, high-performance TUI greeter used for diy mode.
  • mini: A minimal LightDM greeter used for X11/BSPWM sessions.
  • boostgreeter: A pseudo-login path for Wayland sessions that skips a real greeter and boots directly into Hyprland or Niri as the desktop user, then immediately raises hyprlock.

Security & Stability

The system uses System-Wide Icon/Cursor Themes (/run/current-system/sw/share/icons) for all greeters. This ensures that the display manager remains stable and avoids permission crashes that occur when greeters attempt to access themes in restricted user home directories.

Mode Defaults

  • diy mode defaults to tuigreet.
  • dms mode defaults to dms-greeter.
  • Both defaults are overrideable by setting modules.services.desktop.greetd.greeter, which is how boostgreeter can be tested under either shell mode.

Startup Rules

  • Wayland + diy + boostgreeter: the compositor runs hey hook startup, which imports session variables and immediately raises the lock screen.
  • Wayland + diy + tuigreet: no compositor startup hook is needed; tuigreet launches the selected session normally.
  • Wayland + dms + boostgreeter: the compositor runs hey hook startup, which imports session variables, starts dms.service, and then raises the lock screen.
  • Wayland + dms + tuigreet or dms-greeter: no startup hook is used; the compositor starts dms.service directly during normal session startup.
  • X11 + diy + mini: the X session runs hey hook startup, which imports X session variables and immediately raises the lock screen.
  • X11 + dms: unsupported.
  • Wallpaper startup hooks are only attached in diy mode.

🧠 The Wayland Startup Architecture (Lessons Learned)

When configuring a custom greeter or compositor on Wayland, it is vital to respect the logind and systemd user session boundaries:

  1. KMS/DRM and the TTY Requirement: Wayland compositors (like Niri or Hyprland) require direct DRM (Direct Rendering Manager) access to communicate with the GPU. systemd-logind acts as the gatekeeper for this hardware access (seat0). logind will only grant seat access to a session if it is attached to a physical Virtual Terminal (VT). Thus, in greetd.nix, specifying TTYPath = "/dev/tty1"; within serviceConfig is absolutely critical. Without it, logind denies seat access, causing the compositor to falsely assume it is running nested. It will then fall back to attempting an X11 connection (e.g., winit trying to dlopen libXcursor.so.1), which crashes obscurely.
  2. Session Target Orchestration: Simply executing a compositor binary (e.g., niri -c <config>) bypassing the display manager’s standard .desktop session wrappers breaks systemd’s target tree. User-level daemons (like dms.service or waybar) rely on graphical-session.target to start. Proper session wrappers (like niri-session) explicitly execute systemctl --user start niri.service, which natively pulls in graphical-session.target via a BindsTo relationship. Avoid manually executing import-environment or manually starting systemd targets from within the compositor’s config file; rely on the session manager to orchestrate the lifecycle naturally.

🎨 Theme Integration

Desktop environments “decorate” the system by injecting settings into shared modules. These are centralized in modules/themes/desktop/.

  • Priority: Session-enabled settings take precedence.
  • App Reactivity: Apps like Foot or Waybar dynamically adjust their padding, layouts, and font sizes based on which compositor is currently owning the session.