my own playground for experimenting with plugins for the wonderful desktop shell noctalia
Find a file
2026-10-27 09:53:07 +01:00
jira jira: port the tiered, elimination-based window resolver 2026-09-17 22:13:28 +02:00
niri-tabctl fix(niri-tabctl): resolve the window by elimination when its title lies 2026-09-17 22:10:31 +02:00
zj zj: launcher provider to jump back into a project's zellij session 2026-10-27 09:46:34 +01:00
zon-mode zon-mode: bar indicator for the zon-mode switch 2026-10-23 12:07:03 +02:00
.gitignore Add viv-tabs: a /viv launcher mode for Vivaldi tabs 2026-09-16 19:27:53 +02:00
catalog.toml zj: launcher provider to jump back into a project's zellij session 2026-10-27 09:46:34 +01:00
README.md docs: the dev source is the worktree in the config workdir 2026-10-27 09:53:07 +01:00

noctalia-plugins

Personal Noctalia plugins, laid out as a Noctalia plugin source repo: one directory per plugin, plus a catalog.toml at the root indexing them all.

Noctalia v5 plugins are Luau scripts driven by the shell's C++ host. They are not the QML plugins of the v3/v4 (Quickshell) era — those live in the archived legacy-v4-plugins repo and do not load under v5.

Plugins

Plugin Id What it does
niri-tabctl tomster/niri-tabctl /tab — fuzzy-search every open browser tab and jump to it, switching workspace and focusing the window. Needs tabctl; see its README
zj tomster/zj /zj — jump back into a project's zellij session from anywhere: the projects zj has opened, most recent first, running, exited or gone. Enter switches the project's companion terminal to it. See its README
zon-mode tomster/zon-mode Bar indicator for zon-mode (dotfiles): a green Z when this machine is in ZON mode, grey when not. Refreshed by zon-mode's hooks (noctalia msg plugin tomster/zon-mode:indicator all refresh), re-checked every minute

Installing

Two sources are declared in ~/.config/noctalia/settings.toml (managed by chezmoi), so a fresh machine gets both from chezmoi apply:

# Baseline. Noctalia clones and materializes this itself into
# ~/.local/state/noctalia/plugins/, so the plugins work with no local checkout.
[[plugins.source]]
name     = "tomster"
kind     = "git"
location = "https://git.tomster.org/tomster/noctalia-plugins"
enabled  = true

# Dev override, listed last so it wins where it exists.
[[plugins.source]]
name     = "tomster-dev"
kind     = "path"
location = "~/Development/workdirs/config/noctalia-plugins"
enabled  = true

[plugins]
enabled = ["tomster/niri-tabctl"]

HTTPS rather than the SSH remote on purpose: Noctalia runs git non-interactively at startup, so an SSH URL would need an agent-loaded key before the shell starts and would otherwise fail silently.

Note that declaring [[plugins.source]] at all replaces Noctalia's built-in source list rather than appending to it — official and community must be restated verbatim alongside these, or they vanish together with every plugin installed from them.

The equivalent imperative form:

noctalia msg plugins source add tomster git https://git.tomster.org/tomster/noctalia-plugins
noctalia msg plugins source add tomster-dev path ~/Development/workdirs/config/noctalia-plugins
noctalia msg plugins enable tomster/niri-tabctl

The plugin id (tomster/niri-tabctl) is canonical, and a plugin directory name must equal the part of the id after the /.

Precedence

Only one copy of a given author/plugin ever loads. Roots are scanned lowest-to-highest, so the last one wins:

  1. ~/.local/share/noctalia/plugins/<dir>/ — the local drop-in, beats everything
  2. later-declared sources, over earlier ones — hence tomster-dev after tomster
  3. the built-in official and community sources

So on a machine with this repo checked out there (a worktree in the config workdir), edits are live; on one without, Noctalia silently skips the missing path root and serves the git copy. Nothing warns, nothing breaks — a missing path source is an early return in PluginRegistry::scanDir.

You can see which copy won in the log:

plugins] plugin 'tomster/niri-tabctl' at /home/tomster/Development/workdirs/config/noctalia-plugins/niri-tabctl
         overrides earlier copy at .../state/noctalia/plugins/materialized/tomster/niri-tabctl

Where not to edit

Neither state directory is a usable working copy:

  • ~/.local/state/noctalia/plugins/sources/<name>/repo is a blobless clone with no working tree (--filter=blob:none --no-checkout) — there are no files to edit.
  • ~/.local/state/noctalia/plugins/materialized/<name>/<plugin>/ has no .git at all. It is a git checkout --work-tree export that Noctalia atomically replaces on every update, so edits there are destroyed without warning.

Always work in this checkout, commit and push from here.

Adding the git source to a running shell

Git sources are materialized once at startup — PluginManager::refresh() guards the call with if (!m_applied) so a config reload never re-touches the network. A newly declared git source therefore does nothing until the next shell restart. To pick it up immediately:

noctalia msg plugins enable tomster/niri-tabctl   # resolving the source clones + exports it

Developing

# static check: undeclared getConfig keys, missing entry files, unread settings
noctalia plugins lint niri-tabctl

# open the launcher straight into a mode
noctalia msg panel-open launcher "/tab "

# watch the shell log
noctalia msg log-level-set debug
journalctl --user -f -o cat _PID=$(pgrep -x noctalia)

Reload rules, which are not symmetrical:

Changed To pick it up
*.luau nothing — hot-reloads on save, with a "Reloaded script" toast
plugin.toml noctalia msg config-reload
translations/*.json touch the entry .luau — only Luau files are watched, so a config-reload is not enough and every changed key silently renders verbatim

The translations one is a trap: the symptom is plugin translation key '…' not found in the log while the file on disk plainly contains the key.

Reference

  • Plugin docs: docs/user/plugins/development/ in a noctalia checkout — manifest.mdx, entries.mdx, runtime-api.mdx, declarative-ui.mdx, workflow.mdx
  • Ground truth for the noctalia.* API: src/scripting/luau_host.cpp (kNoctaliaBaseLib)
  • Type stubs: noctalia.d.luau in official-plugins
  • ~200 worked examples: community-plugins

Publishing

To offer a plugin to everyone, PR it into community-plugins. That needs a README.md, a thumbnail.webp and translations/en.json alongside the manifest; CI validates them and regenerates that repo's catalog on merge. Directory names there are first-come.