Skip to content

CONFIG_SYSTEM

snowarch edited this page Aug 2, 2026 · 2 revisions

Config System

How configuration works in iNiR, from the user's perspective and from the code side.

For users

Everything is configurable through the graphical Settings UI. Open it with Super+, or inir settings. You should never need to edit the config file by hand.

If you do want to edit it directly, it lives at:

~/.config/illogical-impulse/config.json

(The directory name is a legacy artifact from when iNiR was called illogical-impulse. ~/.config/inir is symlinked to it.)

Changes you make in the file are picked up automatically within 50ms. No restart needed.

Fresh-install profile

A new configuration starts deliberately quiet. Settings opens in Focused mode, the left sidebar contains one curated Widgets tab, and the right sidebar starts with connectivity, sliders, notifications, and four daily tools: Calendar, To Do, Calculator, and System Monitor. Weather, desktop widgets, notification sounds, news feeds, wallpaper search, AI, and anime integrations stay off until you enable them.

Workspace Strip is a preview feature and is not part of either panel family's default module set. It remains available in Settings for explicit opt-in. Existing configurations are not rewritten when these fresh-install defaults change.

The Welcome wizard exposes only choices that materially affect the first session. Advanced styles, additional sidebar tabs, and specialized modules remain available in the full Settings view.

For contributors

The sync rule

Adding a new config key requires updating four things together in one commit:

  1. modules/common/Config.qml - declare the schema property with its type and default
  2. defaults/config.json - add the matching key for fresh installs
  3. Consumer code - the QML that reads or writes the key
  4. Settings UI - if the key is user-facing (most are)

Skip any of these and something breaks silently. The most common mistake is skipping Config.qml, which means Config.options?.your?.key resolves to undefined even though the key exists in defaults.

Reading config

Always null-safe, always with a fallback:

// Standard pattern
readonly property bool enabled: Config.options?.bar?.autoHide?.enable ?? false
readonly property int interval: Config.options?.weather?.interval ?? 15

// Also fine (optional chaining is harmless even when the path exists)
readonly property string city: Config.options?.weather?.city ?? ""

Config properties are available after Config.ready becomes true. Everything that depends on config should gate on this.

Writing config

There is exactly one way to write config that actually persists:

// This works
Config.setNestedValue("bar.autoHide.enable", true)

// This does NOT work (silently fails to persist)
Config.options.bar.autoHide.enable = true

The direct assignment updates the in-memory QML property but never writes to disk. This is the number one source of config bugs. If you see Config.options.x.y = z anywhere, it's a bug.

Schema

Config.qml is a large singleton that defines every config section as typed QML properties. Example:

readonly property QtObject bar: QtObject {
    readonly property bool vertical: root._config?.bar?.vertical ?? false
    readonly property QtObject autoHide: QtObject {
        readonly property bool enable: root._config?.bar?.autoHide?.enable ?? false
        readonly property int showDelay: root._config?.bar?.autoHide?.showDelay ?? 300
    }
}

The schema serves three purposes:

  1. Type safety: properties are typed (bool, int, string, list), not var
  2. Default values: the ?? fallback provides a runtime default even if the key is missing
  3. Documentation: the schema IS the config reference

Defaults

defaults/config.json provides the starting config for fresh installs. It covers ~60 top-level sections.

The defaults file and Config.qml can have different fallback values by design. The defaults file is what gets written to disk on first install. The schema fallbacks are what the code uses if a key is missing at runtime.

Hot-reload

Config uses Quickshell's FileView with watchChanges: true. External edits (from a text editor, a script, whatever) are detected and applied within 50ms. Both reads and writes are debounced at 50ms.

The configChanged signal

setNestedValue emits Config.configChanged() immediately, in the same call, before the debounced 50 ms disk write actually happens. So the signal reflects the new in-memory value, not a confirmed write to disk. Components that need to react to config changes (beyond just re-reading a property) can connect to this signal.

Config sections

The ~60 top-level sections, roughly grouped:

Shell structure: panelFamily, enabledPanels, bar, dock, sidebar

Appearance: appearance (colors, rounding, style, animations), background (wallpaper, blur, widgets)

Services: weather, ai, calendar, search, updates

System: battery, performance, lock, session, idle

Features: notifications, clipboard, screenRecord, nightLight, gameMode

Waffle-specific: waffles (the entire waffle family config namespace)

The full schema is modules/common/Config.qml. The full defaults are defaults/config.json.

Common keys people actually ask about

Bar layout

bar.layout controls the modular ii bar:

  • left
  • centerLeft
  • center
  • centerRight
  • right
  • migrated

Each zone is an array of module ids. Use Settings -> Bar -> Bar module layout unless you are debugging. The editor writes through Config.setNestedValue, so changes persist. The old bar.modulesLayout, bar.edgeModulesLayout, and bar.modulesPlacement keys are legacy compatibility only.

bar.height and bar.opacity control the bar size and background fill. They do not resize every widget independently; components still use the normal Appearance sizing tokens.

Live shell layout

Settings -> Shell Layout and inir shellLayout use the same controller as the live desktop editor. Existing canonical keys remain authoritative:

  • ii bar: bar.vertical plus bar.bottom
  • ii dock: dock.position
  • Waffle taskbar: waffles.bar.bottom

Semantic ii sidebar roles use:

  • sidebar.shellLayout.feature.slot
  • sidebar.shellLayout.feature.sizeMode
  • sidebar.shellLayout.feature.customHeight
  • sidebar.shellLayout.feature.width
  • the matching sidebar.shellLayout.system.* keys

feature is the AI, media, tools and Widgets role historically opened by the sidebarLeft IPC target. system is the quick controls, notifications and utility role historically opened by sidebarRight. Their IPC meaning does not change when the roles swap physical edges.

Desktop widgets keep their original free/zone editor and independent widgetEditMode. Persistent layer-shell surfaces use the separate Shell Layout editor and move between advertised edge slots. Enter it from the desktop context menu, Settings -> Shell Layout, or inir shellLayout open. Its own layer-shell HUD stays above the edited panels and does not reuse the widget canvas or toolbar. Drag any highlighted surface toward a screen edge: legal edges light up as full strips, a chip follows the pointer with the drop result, and releasing on a strip commits the move. Dropping a surface on an occupied edge performs an atomic swap: sidebars exchange sides, and the ii bar and dock exchange edges the same way. Releasing in the center of the screen cancels. The click flow remains for keyboard and scripting: select a surface, choose Move, then activate an edge strip, where occupied edges still ask for a second confirming activation. Resize handles preview locally with a live dimension readout and persist when released; sidebars resize height and width, and the dock resizes its thickness through dock.height.

Bar corner controls are spatial: the bar's left corner and left area open whatever sidebar currently occupies the left edge, and the right-side controls open the right-edge panel, even after a swap. Feature-specific triggers keep opening their own semantic content.

Escape cancels the current resize, lift, preview or swap confirmation first. A second Escape leaves shell edit mode. Done leaves the mode after already committed changes; there is no hidden Save step or whole-session rollback.

Bar, dock and taskbar placement follow each surface's existing screenList semantics. A position change applies to every enabled output for that surface. Desktop-widget placement remains owned by its separate editor. The Shell Layout HUD and Settings page show the broader mutation scope. Per-output geometry profiles are not part of this version.

Valid sidebar slots are left and right. Both roles must occupy different slots, so moving one onto the other performs an atomic swap. Valid size modes are full, fit and custom. Fit uses the active role content: finite feature tabs can contract while unbounded tabs return to full height.

These keys are append-only additions. Existing configs without them retain the historical feature-left and system-right layout, so no migration script is needed. collapseWidgetsTab and collapseEmptyNotifications remain legacy content-aware compatibility options.

Right sidebar header

sidebar.right.headerStyle selects the system section shown at the top of the right sidebar:

  • profile: avatar, account identity, uptime, actions and optional banner media
  • classic: the compact uptime and action row

For the profile style, sidebar.right.headerBanner accepts wallpaper, custom, solid or none. sidebar.right.headerBannerPath stores the local image, GIF or video path used by custom. Wallpaper and animated media playback follow the sidebar's active screen and visibility.

Right sidebar widgets

sidebar.right.enabledWidgets controls the widgets shown in the right sidebar bottom group and compact sidebar.

Known ids include:

calendar, events, todo, notepad, calculator, sysmon, weather, timer, screentime

screentime is only shown when sidebar.screenTime.enable is true. The list can contain it while the service is off; the UI filters it out so disabled tracking does not leave a dead card.

Screen Time

sidebar.screenTime:

  • enable: starts/stops tracking
  • pollIntervalSeconds: focused-window sampling interval
  • retentionDays: how long local daily JSON is kept

Screen Time is local-only. It records app ids/names and seconds, not window titles.

World Clock

sidebar.widgets.worldClock_settings:

  • timezones: explicit IANA timezone ids, e.g. Europe/London
  • showSeconds
  • use24Hour
  • showDate
  • highlightLocal

Empty timezones means the widget can show suggestions based on local timezone/region. Once you add zones in Settings, that explicit list wins.

Wallpaper shuffle

background.autoWallpaper:

  • enable
  • intervalMinutes
  • generateColors
  • folder

If folder is empty, shuffle uses the current wallpaper directory. If generateColors is off, only the image changes; the shell keeps the current palette.

Settings UI

Users interact with config exclusively through Settings:

  • Material ii: Super+, opens an overlay settings panel (lives in modules/settings/)
  • Waffle: Super+, opens a standalone settings window (lives in modules/waffle/settings/)

Both families have their own settings implementations but write to the same config.json. When a config key affects both families, both settings UIs need updating.

Migrations

When a config key is renamed, restructured, or its semantics change in a way that affects existing users, a migration handles the transition. Migrations live in sdata/migrations/ as numbered bash scripts.

Most config additions don't need migrations. A new key with a default just appears in the schema and existing users get the default value. Migrations are only for breaking changes to existing keys.

Clone this wiki locally