-
-
Notifications
You must be signed in to change notification settings - Fork 78
RUNTIME
What happens between "user logs in" and "shell is on screen", step by step.
User logs in
|
Display manager starts Niri (or Hyprland)
|
Compositor reaches graphical-session.target
|
systemd starts inir.service (wants link from compositor)
|
ExecStart calls: /usr/bin/inir run --session
|
inir script (bash):
- Validates QS/Qt ABI compatibility
- Sets QT_SCALE_FACTOR=1
- Suppresses noisy Qt log categories
- Bridges niri env vars to systemd session
- Launches: qs -c inir
|
Quickshell loads shell.qml
|
ShellRoot initialization:
1. Force-instantiate Idle and PowerProfilePersistence
2. Load FirstRunExperience (checks if first run)
3. Load ConflictKiller (kills conflicting trays/notification daemons)
4. Wait for Config.ready
|
Config.ready fires:
1. Apply current theme (ThemeService.applyCurrentTheme)
2. Initialize icon theme
3. Migrate enabledPanels if needed
4. Start shell entry timer (200ms delay for animation)
5. Schedule deferred init (500ms for non-critical services)
|
Panel loading:
- Selects ShellIiPanels or ShellWafflePanels based on panelFamily
- Each PanelLoader activates when its conditions are met
- Immediate panels load first (bar, background, OSD)
- Deferred panels load after GlobalStates.deferredPanelsReady
|
Shell entry animation completes
|
Deferred services load (GameMode, Weather, etc.)
|
Shell fully operational
The systemd service is the key piece. It does not use systemctl enable in the traditional sense because there's no [Install] section. Instead, iNiR creates a wants link from your compositor's service:
Niri: ~/.config/systemd/user/niri.service.wants/inir.service
Hyprland: ~/.config/systemd/user/wayland-wm@Hyprland.service.wants/inir.service
This means iNiR starts when your compositor starts and stops when it stops. It will never accidentally start under KDE or GNOME.
Managing the link:
inir service enable # create the wants link
inir service disable # remove it
inir service status # check current statescripts/inir is a 3600+ line bash script that wraps Quickshell. It's not the same as running qs -c inir directly:
inir run |
qs -c inir |
|
|---|---|---|
| Environment setup | Sets QT_SCALE_FACTOR, suppresses warnings, bridges niri env | Raw environment |
| Output | Backgrounded, logs to journal | Foreground, direct stdout |
| Crash recovery | systemd restarts on failure (max 3 in 30s) | None |
| ABI check | Validates Quickshell/Qt compatibility | None |
| Orphan cleanup | ExecStopPost cleans stale runtime | None |
For development and debugging, qs -c inir (direct mode) is usually better because you get stdout immediately. For daily use, the systemd service handles everything.
The launcher sets these before starting Quickshell:
| Variable | Value | Why |
|---|---|---|
QT_SCALE_FACTOR |
1 |
Shell handles its own scaling in QML |
QT_SCALE_FACTOR_ROUNDING_POLICY |
RoundPreferFloor |
Prevents blurry rendering with fractional compositor scaling (1.25 etc) |
QT_LOGGING_RULES |
(long list) | Suppress known-harmless Qt/QML warnings |
The launcher also unsets inherited DPI variables that would cause blur: QT_WAYLAND_FORCE_DPI, QT_FONT_DPI, QT_AUTO_SCREEN_SCALE_FACTOR, QT_SCREEN_SCALE_FACTORS, GDK_SCALE, GDK_DPI_SCALE.
The --session flag (used by systemd) also runs ensure_systemd_graphical_env in the background, which bridges critical Niri environment variables to the systemd user session. Without this, apps launched from the shell wouldn't get WAYLAND_DISPLAY, NIRI_SOCKET, or ELECTRON_OZONE_PLATFORM_HINT.
Config.qml uses Quickshell's FileView to read the user's JSON config file. The loading sequence:
- FileView reads
~/.config/illogical-impulse/config.json - JsonAdapter parses the content
- Schema properties bind to parsed values (with fallbacks)
-
Config.readybecomes true - Everything that was waiting on config starts loading
If the config file doesn't exist (fresh install), Config creates it from defaults/config.json.
Hot-reload: if you edit config.json externally, FileView detects the change and re-parses within 50ms.
Each panel is wrapped in a PanelLoader:
PanelLoader {
identifier: "iiBar"
extraCondition: !(Config.options?.bar?.vertical ?? false)
component: Bar {}
}A panel loads when all three conditions are true:
-
Config.readyis true - The identifier exists in
Config.options.enabledPanels -
extraConditionevaluates to true
Panels are split into immediate (load at first frame) and deferred (load after deferredPanelsReady):
Immediate: bar, background, notification popup, OSD. These need to be visible right away.
Deferred: sidebars, overview, clipboard, lock screen, cheatsheet. These load after the shell is already on screen and responsive.
The systemd service has:
-
Restart=on-failurewithStartLimitBurst=3andStartLimitIntervalSec=30 - If iNiR crashes, systemd restarts it (up to 3 times in 30 seconds)
-
ExecStopPostrunsinir cleanup-orphansto clear stale Quickshell runtime entries - Exit code 143 (SIGTERM) is treated as success, not failure
Non-critical services load 500ms after the first frame to reduce boot contention:
- GameMode (fullscreen detection)
- WindowPreviewService (alt-tab previews)
- Weather (API polling)
- VoiceSearch (Gemini transcription)
- FontSyncService (GTK/KDE font sync)
- Hyprsunset (night light)
This keeps the initial frame fast. The bar and background appear immediately, everything else fills in shortly after.
If the shell won't start:
# Direct stdout (bypass systemd)
qs -c inir
# Verbose internal logging
qs -v -c inir
# Extra verbose
qs -vv -c inir
# Debug-level service logging
QS_DEBUG=1 qs -c inirCheck inir logs for recent journal output, or inir doctor for automated diagnostics.
inir doctor --perf prints a read-only snapshot of the running shell. It reports
process memory and threads, the observed Qt Quick RHI and render loop, DRM render
nodes, Qt Multimedia decoder ownership, open video and GIF files, mapped Niri
layer surfaces, child processes, and the inir.service cgroup.
inir doctor --perfUse it before changing graphics environment variables or blaming one feature for the complete service cgroup. The snapshot distinguishes Quickshell from helpers and applications, and flags duplicate media files opened by more than one renderer.
iNiR documentation. main is stable. prerelease is development.