Skip to content
This repository was archived by the owner on Oct 20, 2026. It is now read-only.
sysv86Public archive

About

Material 3 desktop shell for Niri and Hyprland, built on IgnisLua

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Exo

A Material 3 desktop shell for Niri and Hyprland, running on IgnisLua.

This is a Lua port of Exo (Python Ignis). The whole configuration is plain Lua and SCSS: no Python, no runtime code generation.

What is included:

  • bar with nine widgets (launcher, window info, media, workspaces, tasks, keyboard layout, system info tray, telemetry, clock)
  • launcher (grid and list modes, pinned apps, search, app actions)
  • quick center (tiles, sliders, notification list)
  • notification cards, popups and history
  • OSD, power menu, screen and shell corner overlays
  • Material 3 Expressive component set and showcase window
  • static themes (themes/landroid.lua, six Landroid variants) that colour the whole shell without a wallpaper or matugen
  • settings window with seven tabs (appearance, quick, bar, launcher, network, Bluetooth, services)

Running

Exo needs IgnisLua — the shell is a configuration, the framework runs it. Clone both side by side, then:

# from the IgnisLua checkout, pointing at this config
./bin/ignis-bin init -c ../Exo/config.lua
lua5.5 bin/ignis init -c ../Exo/config.lua   # or through the Lua CLI

Or install the config as a user configuration and run it from anywhere:

./scripts/install_exo.sh                     # IgnisLua helper: copies this
                                             # directory to ~/.config/ignis-lua
ignis init -c ~/.config/ignis-lua/config.lua

Window and command control (bind these in the compositor):

ignis toggle-window Launcher
ignis toggle-window QuickCenter
ignis open-window Settings
ignis open-window PowerMenu
ignis run-command recorder-record-screen
ignis run-command recorder-record-region
ignis run-command set-wallpaper /path/to/wallpaper.png

Layout

config.lua                     entry point
user_settings.lua              settings tree with JSON persistence
colors.scss                    Material 3 palette (regenerated by the wallpaper script)
paths.lua                      config-relative paths
widget_proxy.lua               helper for wrappers around GTK widgets
modules/
  compositor.lua               Niri/Hyprland abstraction
  bar/                         bar and its widgets
  launcher/                    app launcher
  quickcenter/                 quick center and sliders
  notifications/               notification cards and popups
  osd/ powermenu/ corners/     overlays
  m3components/                Button, NewButton, Slider, NavigationRail,
                               connected button groups, M3 shape engine
  m3test/                      Material 3 showcase window
  settings/                    settings window and tabs
scripts/                       theming, bar styles, launcher layout, recorder,
                               auto dark mode, notifications
styles/                        SCSS
matugen/                       matugen config and templates

Dependencies

Required: Niri or Hyprland, GTK4, libgtk4-layer-shell, and the Material Symbols Outlined font — icons are ligatures, so without the font the buttons show icon names as text:

mkdir -p ~/.local/share/fonts
curl -L -o ~/.local/share/fonts/MaterialSymbolsOutlined.ttf \
  'https://github.com/google/material-design-icons/raw/master/variablefont/MaterialSymbolsOutlined%5BFILL%2CGRAD%2Copsz%2Cwght%5D.ttf'
fc-cache -f

Optional, with graceful degradation when absent:

Tool Used for Without it
matugen wallpaper-driven colour schemes the bundled colors.scss palette is used and the static themes in Settings → Appearance still work
swww setting the wallpaper matugen still regenerates colours
gpu-screen-recorder screen recording the recorder reports "No recorder"
slurp region recording region recording reports a missing dependency
hyprlock lock screen the power menu falls back to loginctl lock-session
dart-sass/grass SCSS compilation IgnisLua's built-in Lua SCSS compiler is used
gnome-control-center Settings → Network/Bluetooth shortcuts a notification says it is not installed

The desktop notification daemon is a singleton: if another shell already owns org.freedesktop.Notifications, Exo cannot take it and its popups and notification center stay empty.

Framework changes made for this port

The port required a few additions and fixes in IgnisLua itself:

  • ignis/utils/scss.lua — dependency-free SCSS compiler (@use, $variables, namespaced variables, nesting/&, @keyframes, @media, rgba() with hex or named colours, Sass mix()). ignis/utils/sass.lua falls back to it when no external compiler is installed.
  • ignis/options_manager.lua — OptionsManager/OptionsGroup with JSON persistence, generated set_* accessors, connect_option and widget bindings; this is Exo's entire settings layer.
  • ignis/dbus.lua — D-Bus object export works on the LuaGObject backend (a GObject.Closure is passed where the marshaller expects one), which makes IPC, notifications and the tray functional; error replies use return_dbus_error.
  • src/ignis_launcher.c — bin/ignis-bin no longer crashes: Lua 5.5 removed luaL_openlibs, so standard libraries are opened through luaL_requiref, every dlsym result is checked, the bundled LuaGObject path is added, and IGNIS_NO_PRELOAD=1 skips library pre-binding.
  • ignis/base_widget.lua — reactive visible bindings, string enum setters (widget:set_halign("start")), set_width_request/ set_height_request shims (GTK4 has no such methods), and retention of signal pads and callbacks.
  • ignis/config_manager.lua — the config directory is added to package.path for both ?.lua and ?/init.lua.
  • ignis/services/fetch.lua — gtk_theme and icon_theme.
  • ignis/window_manager.lua — suppress_osd, used by the OSD and sliders.

Stability notes

Four crash causes and one deadlock were found and fixed while bringing the shell up on Wayland.

Deadlock: the shell keeps its last frame and stops updating

Cause: NetworkService subscribes to NetworkManager GObject signals. Those are emitted from inside libnm's own D-Bus message dispatch, and the handler called sync_state(), which reads properties with synchronous D-Bus calls. A synchronous call on the connection being dispatched never completes, so the main loop blocked forever (the process sat in ppoll, IPC stopped answering, pointer events were still delivered).

Fix: D-Bus signal callbacks (DBusProxy:signal_subscribe, watch_properties) and NetworkManager GObject handlers now run deferred through an idle/timeout callback. scripts/run_exo.sh also restarts the shell if IPC stops answering.

Crashes

  1. Invalid GType passed to GI. base_widget probed properties with GObject.ObjectClass.find_property(self._class, prop); the class record reached through self._class can be stale, which produced gi_repository_find_by_gtype: assertion 'gtype != G_TYPE_INVALID' failed and a SIGSEGV in g_type_fundamental(). notify() and connect() now probe the property on the instance inside pcall.
  2. Negative minimum size in the Material 3 slider. min-height: 24px with margin: -16px 0px made GTK compute -8 and crash while laying out the quick center sliders. The stylesheet now uses margin: 0px for the slider and its hidden value label.
  3. Measuring the slider value label. Exo passed draw_value = true with value_pos = "top" while its CSS hides that label; GTK crashed while measuring it. The flags are dropped — visually identical.
  4. LuaGObject freeing the GClosure behind a Lua signal callback while the signal is still connected, so the next emission jumped through a dangling pointer (g_signal_emit → g_closure_invoke → <junk>). IgnisLua now retains every signal pad and callback, and the LuaGObject fork only unrefs objects it actually owns.

Contributing

Use the issue and pull request templates in .github/, and read CONTRIBUTING.md first.

License

LGPL-2.1-or-later, matching IgnisLua. Exo upstream is MIT licensed.

About

Material 3 desktop shell for Niri and Hyprland, built on IgnisLua

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages