Exo Modules #
Structure #
Exo features live in ignis/modules/<name>/. Each module must have a module.py manifest exporting MODULE.
ignis/core/contains the registry, module types and shared code.ignis/modules/contains Exo Modules.m3componentsandshared_modulesare shared code, not Exo Modules.ignis/config.pyloads the registry and attaches it toapp.registry.
Import core.*, not ignis.core.*. The latter is not the Exo core package.
Manifest #
A module directory must match its ID ([a-z][a-z0-9_]*). Directories without a manifest are ignored.
from core.module import Module
def setup(ctx):
from .impl import create_service
ctx.provide("my_service", create_service())
MODULE = Module(
id="my_module",
requires=(),
provides=("my_service",),
needs=(),
settings=None,
setup=setup,
settings_tabs=(),
bar_widgets=(),
styles=None,
nix_options=None,
enabled_by_default=True,
)
Module fields:
requires: other module IDsprovides: services provided by this moduleneeds: required servicessettings: optional JSON file pathsetup: module setup function receiving aContextsettings_tabs:SettingsTabdefinitionsbar_widgets:BarWidgetdefinitionsstyles: optional stylesnix_options: optional Nix optionsenabled_by_default: defaults toTrue
SettingsTab(id, title, icon, order, group, factory) defines a settings tab. Its factory is called when needed. Groups are ordered as Shell, Connections, System. Unknown groups are sorted alphabetically after them.
BarWidget(id, factory, default_location, default_bar_id, default_visible, name, description) defines a bar widget. Bar widgets must stay inside bar for now.
Keep manifests import-light. Do not import gi, ignis, user_settings or anything that imports them at the top level. Use lazy imports inside setup() and factories instead. This also applies to disabled modules because all manifests are read before startup. tests/test_manifest_imports.py checks this.
Registry #
core.registry scans modules/*/module.py and loads MODULE.
Module enablement follows this priority:
EXO_MODULE_<ID>environment variable (1,true,yes,on,0,false,no,off).modules.json, generated by Nix fromexo.modules.<id>.enable.enabled_by_defaultin the manifest.
Invalid environment values produce a warning. Module states are decided at startup.
requires uses module IDs. provides and needs use service names. Missing dependencies or required services cause the affected module to be skipped. Modules are loaded in dependency order, with alphabetical order used for ties.
The registry catches discovery and setup errors, logs the module ID and traceback, and skips the affected module. Modules depending on a failed module are skipped too. Failed setup side effects are not rolled back.
Context and services #
Every setup() receives a Context. Modules should communicate through services and signals instead of importing each other directly.
ctx.provide("name", service) # Register a service
ctx.get("name") # Optional dependency, returns None if missing
ctx.require("name") # Required dependency, raises if missing
core must not depend on specific Exo Modules.
Settings and UI #
Settings are stored in shared JSON files:
ignis/core/settings.jsonignis/modules/<id>/settings.json
Python uses them to build ExoGroup and OptionsManager. Nix reads the same files with builtins.fromJSON and readDir. No IFD is needed.
Settings tabs and bar widgets are declared in the module manifest. The settings UI combines tabs using modules/settings/tab_layout.py.
Keep the existing settings JSON structure, exo.settings.* paths and public Nix options unchanged.
Bar widgets stay inside bar. The interface.modules.* namespace and "Bar Modules" label are kept for compatibility. Use "bar widget" in new code and documentation. BarWidget is not an Exo Module.
If two settings tabs have the same ID, the later one wins and a warning is logged.
Styles #
Modules can provide styles through Module.styles. They are merged at runtime into a writable overlay at:
state_path("exo", "styles-overlay", "ignis")
Styles are loaded in this order:
bar, dock, corners, settings, network, bluetooth, sidebar, launcher, powermenu, quickcenter, notifications, osd, m3, previews
Unknown styles produce a warning and are placed last. No manual @use statements are needed.
Nix #
Use exo.modules.<id>.enable to enable or disable modules. Nix writes the result to modules.json next to the seed file.
Settings are exposed through exo.settings.* and read from the shared JSON files. The old hand-written toJSON in settings.nix is no longer used.
Other Nix options belong in nix/options/*.
Notes #
- Do not use
utils.Pollor blindly resample values. Use GObject/GLib signals and cancellableGLib.timeout*calls. - Keep compatibility with existing settings, Nix paths and lock behaviour.
- Compatibility wrappers in
scripts/may remain during migration. ignis/modules/_template/contains a minimal manifest template. Copy it into a properly named module directory before using it.
The registry currently includes corners, launcher, notifications, osd, m3test, powermenu, quickcenter, bluetooth, network, settings, bar, wallpaper and recorder. Other services include bar_styles and auto_dark.