docs: standardize and validate plugin READMEs

This commit is contained in:
Lemmy
2026-07-15 21:56:42 -04:00
parent 1148c2d698
commit c057379c91
17 changed files with 700 additions and 70 deletions
+3
View File
@@ -35,6 +35,9 @@
- [ ] The directory name matches the part of `id` after the `/` in `plugin.toml` exactly.
- [ ] It ships `plugin.toml`, `README.md`, `thumbnail.webp`, and `translations/en.json`.
- [ ] `README.md` follows the
[README template](https://github.com/noctalia-dev/community-plugins/blob/main/README_TEMPLATE.md), documents
every entry id and dependency, and includes exact panel IPC commands and launcher prefixes where applicable.
- [ ] I created `thumbnail.webp` with the [thumbnail generator](https://assets.noctalia.dev/plugins/thumbnail-generator.html).
- [ ] `version` follows semver and is bumped in this PR; `min_noctalia` is the version I tested against.
- [ ] Every non-English translation in this PR uses a locale supported by Noctalia core, and I can read, write, and
@@ -1,6 +1,7 @@
from __future__ import annotations
import importlib.util
import tempfile
import unittest
from pathlib import Path
@@ -103,5 +104,100 @@ class PluginConfigAccessorTests(unittest.TestCase):
self.assertEqual(validate_plugins.obsolete_config_accessors(source), [])
class ReadmeTests(unittest.TestCase):
MANIFEST = {
"id": "me/example",
"dependencies": ["example-cli"],
"setting": [{"key": "interval"}],
"widget": [{"id": "widget", "entry": "widget.luau"}],
"panel": [{"id": "panel", "entry": "panel.luau"}],
"launcher_provider": [
{"id": "search", "entry": "launcher.luau", "prefix": "ex"}
],
}
VALID_README = """# Example
Example provides a useful widget, panel, and launcher for demonstration purposes.
## Plugin
| Field | Value |
| --- | --- |
| ID | `me/example` |
| Entries | Widget: `widget`; panel: `panel`; launcher: `search` |
| Launcher Prefix | `/ex` |
## Requirements
Install `example-cli` on `PATH`.
## Usage
Add the widget, type `/ex`, or open the panel:
```sh
noctalia msg panel-toggle me/example:panel
```
## Settings
Configure the update interval in plugin settings.
"""
def validate_readme(self, contents: str) -> list[str]:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
plugin_dir = root / "example"
plugin_dir.mkdir()
(plugin_dir / "README.md").write_text(contents, encoding="utf-8")
validator = validate_plugins.Validator(root)
validator.validate_readme(plugin_dir, self.MANIFEST)
return validator.errors
def test_accepts_official_plugin_readme_structure(self) -> None:
self.assertEqual(self.validate_readme(self.VALID_README), [])
def test_requires_core_sections_and_intro(self) -> None:
errors = self.validate_readme("# Example\n\nToo short.\n")
self.assertTrue(any("short introduction" in error for error in errors))
self.assertTrue(any("## Plugin" in error for error in errors))
self.assertTrue(any("## Usage" in error for error in errors))
def test_headings_inside_code_fences_do_not_satisfy_sections(self) -> None:
errors = self.validate_readme(
"# Example\n\nA sufficiently descriptive introduction for this example plugin.\n\n"
"```md\n## Plugin\n## Usage\n```\n"
)
self.assertTrue(any("## Plugin" in error for error in errors))
self.assertTrue(any("## Usage" in error for error in errors))
def test_derives_documented_values_from_manifest(self) -> None:
readme = self.VALID_README
replacements = {
"`me/example`": "`me/wrong`",
"`widget`": "`other-widget`",
"noctalia msg panel-toggle me/example:panel": "noctalia msg panel-toggle me/example:wrong",
"`/ex`": "`/wrong`",
"`example-cli`": "`other-cli`",
}
for old, new in replacements.items():
with self.subTest(missing=old):
errors = self.validate_readme(readme.replace(old, new))
self.assertTrue(errors)
def test_requires_conditional_sections(self) -> None:
without_requirements = self.VALID_README.replace(
"## Requirements\n\nInstall `example-cli` on `PATH`.\n\n", ""
)
without_settings = self.VALID_README.replace(
"## Settings\n\nConfigure the update interval in plugin settings.\n", ""
)
self.assertTrue(
any("## Requirements" in error for error in self.validate_readme(without_requirements))
)
self.assertTrue(any("## Settings" in error for error in self.validate_readme(without_settings)))
if __name__ == "__main__":
unittest.main()
+150 -2
View File
@@ -151,6 +151,7 @@ HTML_RE = re.compile(
)
INLINE_CODE_RE = re.compile(r"(?<!`)(`+)(?!`)(.*?)(?<!`)\1(?!`)", re.DOTALL)
FENCE_OPEN_RE = re.compile(r"^ {0,3}(`{3,}|~{3,})")
ATX_HEADING_RE = re.compile(r"^ {0,3}(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$")
OBSOLETE_CONFIG_ACCESSOR_RE = re.compile(
r"\b(barWidget|desktopWidget|panel|launcher)\s*\.\s*getConfig\b"
)
@@ -211,6 +212,50 @@ def raw_html_line(markdown: str) -> int | None:
return text.count("\n", 0, match.start()) + 1
def markdown_headings(markdown: str) -> list[tuple[int, str, int, int]]:
"""Return ATX headings outside fenced code as (level, title, start, end)."""
headings: list[tuple[int, str, int, int]] = []
fence_char = ""
fence_length = 0
offset = 0
for line in markdown.splitlines(keepends=True):
stripped = line.rstrip("\r\n")
if fence_char:
closing = rf"^ {{0,3}}{re.escape(fence_char)}{{{fence_length},}}\s*$"
if re.match(closing, stripped):
fence_char = ""
fence_length = 0
offset += len(line)
continue
opening = FENCE_OPEN_RE.match(line)
if opening:
fence = opening.group(1)
fence_char = fence[0]
fence_length = len(fence)
offset += len(line)
continue
match = ATX_HEADING_RE.match(stripped)
if match:
headings.append((len(match.group(1)), match.group(2).strip(), offset, offset + len(line)))
offset += len(line)
return headings
def section_body(markdown: str, headings: list[tuple[int, str, int, int]], index: int) -> str:
"""Return a heading's body, including nested subsections."""
level, _title, _start, body_start = headings[index]
body_end = len(markdown)
for next_level, _next_title, next_start, _next_end in headings[index + 1 :]:
if next_level <= level:
body_end = next_start
break
return markdown[body_start:body_end].strip()
def obsolete_config_accessors(source: str) -> list[tuple[str, int]]:
"""Find removed entry-specific getConfig aliases outside Luau comments and strings."""
visible = list(source)
@@ -924,7 +969,7 @@ class Validator:
f"Export one with {THUMBNAIL_GENERATOR_URL}",
)
def validate_readme(self, plugin_dir: Path) -> None:
def validate_readme(self, plugin_dir: Path, manifest: dict[str, Any]) -> None:
readme = plugin_dir / "README.md"
if not readme.is_file():
return
@@ -939,6 +984,109 @@ class Validator:
if line is not None:
self.add_error(readme, f"raw HTML on line {line} is not allowed; use Markdown instead")
headings = markdown_headings(contents)
h1_indexes = [index for index, heading in enumerate(headings) if heading[0] == 1]
if not h1_indexes:
self.add_error(readme, "missing a level-one plugin title ('# Plugin Name')")
else:
h1_index = h1_indexes[0]
intro_start = headings[h1_index][3]
intro_end = headings[h1_index + 1][2] if h1_index + 1 < len(headings) else len(contents)
intro = contents[intro_start:intro_end]
intro_words = re.findall(r"[A-Za-z0-9][A-Za-z0-9'_-]*", intro)
if len(intro_words) < 8:
self.add_error(readme, "add a short introduction below the title explaining what the plugin does")
h2_by_name = {
title.casefold(): index
for index, (level, title, _start, _end) in enumerate(headings)
if level == 2
}
for section in ("Plugin", "Usage"):
index = h2_by_name.get(section.casefold())
if index is None:
self.add_error(readme, f"missing required '## {section}' section")
elif not section_body(contents, headings, index):
self.add_error(readme, f"'## {section}' section must not be empty")
plugin_section_index = h2_by_name.get("plugin")
plugin_section = (
section_body(contents, headings, plugin_section_index)
if plugin_section_index is not None
else ""
)
plugin_id = manifest.get("id")
if not is_non_empty_string(plugin_id):
return
documented_id = f"`{plugin_id}`"
if documented_id not in plugin_section:
self.add_error(readme, f"Plugin section must document the manifest id as {documented_id}")
for entry_type in ENTRY_TYPES:
entries = manifest.get(entry_type, [])
if not isinstance(entries, list):
continue
for entry in entries:
if not isinstance(entry, dict) or not is_non_empty_string(entry.get("id")):
continue
entry_id = entry["id"]
documented_entry = f"`{entry_id}`"
if documented_entry not in plugin_section:
self.add_error(
readme,
f"Plugin section must document {entry_type} entry '{entry_id}' as {documented_entry}",
)
if entry_type == "panel":
command = f"noctalia msg panel-toggle {plugin_id}:{entry_id}"
if command not in contents:
self.add_error(
readme,
f"missing panel IPC command; add: {command}",
)
if entry_type == "launcher_provider" and is_non_empty_string(entry.get("prefix")):
prefix = f"`/{entry['prefix']}`"
if prefix not in plugin_section:
self.add_error(
readme,
f"missing launcher prefix {prefix} for entry '{entry_id}'",
)
dependencies = manifest.get("dependencies", [])
if isinstance(dependencies, list) and dependencies:
requirements_index = h2_by_name.get("requirements")
if requirements_index is None:
self.add_error(readme, "plugins with dependencies require a '## Requirements' section")
requirements = ""
else:
requirements = section_body(contents, headings, requirements_index)
if not requirements:
self.add_error(readme, "'## Requirements' section must not be empty")
for dependency in dependencies:
if is_non_empty_string(dependency) and f"`{dependency}`" not in requirements:
self.add_error(
readme,
f"Requirements must mention manifest dependency `{dependency}`",
)
has_settings = bool(manifest.get("setting"))
for entry_type in SETTING_OWNER_TYPES:
entries = manifest.get(entry_type, [])
if isinstance(entries, list) and any(
isinstance(entry, dict) and bool(entry.get("setting")) for entry in entries
):
has_settings = True
break
if has_settings:
settings_index = h2_by_name.get("settings")
if settings_index is None:
self.add_error(readme, "plugins with settings require a '## Settings' section")
elif not section_body(contents, headings, settings_index):
self.add_error(readme, "'## Settings' section must not be empty")
def validate_luau_api(self, plugin_dir: Path) -> None:
for source_path in sorted(plugin_dir.rglob("*.luau")):
try:
@@ -969,7 +1117,7 @@ class Validator:
self.validate_root_fields(manifest_path, manifest)
self.validate_required_files(manifest_path, plugin_dir)
self.validate_thumbnail(manifest_path, plugin_dir)
self.validate_readme(plugin_dir)
self.validate_readme(plugin_dir, manifest)
self.validate_luau_api(plugin_dir)
self.validate_no_symlinks(manifest_path, plugin_dir)
+19
View File
@@ -102,6 +102,25 @@ the **[thumbnail generator](https://assets.noctalia.dev/plugins/thumbnail-genera
your plugin, set the title, category tag and accent color, then export the 960×540 WebP and commit it as
`<plugin>/thumbnail.webp`.
### README
`README.md` is the plugin's public page, so it must tell a user how to access every entry instead of only describing
the implementation. Follow [`README_TEMPLATE.md`](README_TEMPLATE.md), which mirrors the structure used by the
official plugins:
- Start with a title, a short explanation, a `Plugin` table, and practical `Usage` instructions.
- Copy the plugin id and every entry id exactly from `plugin.toml`.
- If the plugin declares a panel, include the exact command
`noctalia msg panel-toggle <author>/<plugin>:<panel-id>`.
- If it declares a launcher provider, document its `/<prefix>` and give an example query.
- Mention every manifest dependency under `Requirements`, using the exact dependency name.
- Document declared settings, including units or non-obvious effects.
- Use `IPC` and `Notes` when the plugin exposes extra events or has important filesystem, network, process, privacy,
hardware, or compositor behavior.
CI derives ids, panel commands, launcher prefixes, dependencies, and whether settings exist from `plugin.toml`. Its
error messages show the exact missing value, while maintainers review the usefulness and accuracy of the prose.
### Translations
Write `translations/en.json` only. Every `label_key` and `description_key` in your manifest must resolve to a key in
+61
View File
@@ -0,0 +1,61 @@
# Plugin Name
Explain in one or two sentences what the plugin does and why someone would use
it.
## Plugin
<!-- Copy ids exactly from plugin.toml. Remove rows that do not apply. -->
| Field | Value |
| --- | --- |
| ID | `<author>/<plugin>` |
| Entries | Bar widget: `<widget-id>`; panel: `<panel-id>`; service: `<service-id>` |
| Launcher Prefix | `/<prefix>` |
## Requirements
<!-- Required when plugin.toml declares dependencies. Mention every dependency
using its exact manifest name, for example `example-cli`. Include any
authentication, hardware, service, or compositor requirements too. Remove
this section only when the plugin has no requirements. -->
Install `example-cli` on `PATH`.
## Usage
Explain how to add or access every user-facing entry and describe the normal
workflow. Use exact labels and ids. For a panel, include its copy-pasteable IPC
command:
```sh
noctalia msg panel-toggle <author>/<plugin>:<panel-id>
```
For a launcher provider, explain what to type after `/<prefix>` and what
activating a result does. For a shortcut, say where users add it in Settings.
## Settings
<!-- Required when plugin.toml declares settings. Describe behavior and units,
especially for settings whose effect is not obvious from the label. A
table like the official plugin READMEs is recommended. -->
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `example_setting` | `bool` | `false` | What changing this setting does. |
## IPC
<!-- Optional unless the plugin exposes actions beyond opening a panel. List
exact commands and explain their arguments and effects. -->
```sh
noctalia msg plugin <author>/<plugin>:<entry-id> all <event> [payload]
```
## Notes
<!-- Optional. Document important side effects and limitations: network access,
files written, commands spawned, sensitive data, compositor support, and
useful debugging information. -->
+29 -5
View File
@@ -1,10 +1,34 @@
# Daily Wallpaper
Fetches a daily wallpaper from Bing or NASA and applies it through the Noctalia 5 wallpaper API.
Daily Wallpaper fetches Bing's image of the day or NASA's image of the day and
applies it through Noctalia's wallpaper API.
The service checks on startup and then every 10 minutes. It downloads at most one image per source and Bing locale per day, stores its files in a dedicated `daily-wallpaper` directory, and removes cached images older than 5 days. Repeated failures are logged, but error notifications are limited to once per day.
## Plugin
Settings:
| Field | Value |
| --- | --- |
| ID | `nzlov/daily-wallpaper` |
| Entry | Service: `service` |
- Source: Bing or NASA.
- Locale: Bing market locale such as `en-US`, `de-DE`, or `fr-FR`. NASA ignores this setting.
## Usage
Enable the plugin in Settings → Plugins. Its headless service checks for the
current image on startup and every ten minutes, applying at most one new image
per source and Bing locale each day.
Choose Bing or NASA under the plugin's settings. Bing accepts a market locale
such as `en-US`, `de-DE`, or `fr-FR`; NASA ignores the locale setting.
## Settings
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `source` | `select` | `bing` | Selects Bing or NASA as the daily image source. |
| `locale` | `string` | *(automatic)* | Bing market locale; an empty value uses the service default. |
## Notes
The service contacts the selected provider and downloads images into a
dedicated `daily-wallpaper` cache directory. It removes cached images older
than five days. Repeated failures are logged, but error notifications are
limited to once per day.
+44 -12
View File
@@ -1,16 +1,48 @@
iio-lock — Orientation Lock for 2‑in‑1 Devices
# iio-lock
iio-lock is a small bar widget and panel plugin for Noctalia that provides manual control over screen orientation.
It works by stopping and restarting the iio-hyprland service (or similar IIO rotation tools), preventing automatic rotation while the lock is active.
iio-lock provides orientation controls for 2-in-1 devices, allowing automatic
screen rotation to be locked and manual output transforms to be selected.
This is useful on 2‑in‑1 touch laptops, where automatic rotation may not always be desired — such as when using the device in tablet mode, drawing, or note‑taking.
## Plugin
Supported WM and Tools
Hyprland / iio‑hyprland — Working and tested
Sway / iio‑sway — Unverified (experimental)
| Field | Value |
| --- | --- |
| ID | `nikolaj-zwergius/iio_lock` |
| Entries | Bar widgets: `iio-lock`, `lock-panel`; panel: `panel`; shortcut: `toggle`; service: `iio-service` |
External commands
This plugin shells out to:
-pgrep
-pkill
-iio-hyprland (or the user‑configured tool)
## Requirements
The plugin supports Hyprland with `iio-hyprland` and `hyprctl`. Sway support
uses `iio-sway` and `swaymsg` and is currently experimental. The helper
commands `pgrep` and `pkill` must also be available on `PATH`.
## Usage
Add the `iio-lock` widget to toggle the orientation lock with a left click and
open the transform panel with a right click. The `lock-panel` widget opens the
transform panel with a left click. You can also add the `toggle` shortcut under
Settings → Control Center shortcuts.
Open the transform panel directly with:
```sh
noctalia msg panel-toggle nikolaj-zwergius/iio_lock:panel
```
Locking stops the configured IIO rotation helper; unlocking starts it again.
The panel applies the selected transform to the chosen output.
## Settings
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `iio` | `select` | `iio-hyprland` | Automatic rotation helper to start and stop. |
| `transform-order` | `string` | `0,1,2,3` | Transform values shown by the panel, in order. |
| `vm` | `select` | `hyprland` | Selects the Hyprland or Sway command backend. |
| `locked` | `glyph` | `lock` | Glyph used while orientation is locked. |
| `unlocked` | `glyph` | `lock-open-2` | Glyph used while orientation is unlocked. |
## Notes
Hyprland is the tested backend. Verify the configured rotation helper works on
its own before using the plugin, especially on Sway and similar compositors.
+28 -3
View File
@@ -1,3 +1,28 @@
# mangowm-keymode
![mangowm keymode thumbnail](thumbnail.webp)
**Mangowm Keymode** is a bar widget plugin written for Noctalia (v5) that displays the current keymode from window manager [MANGOWM](https://mangowm.github.io/). It also notifies you when the keymode changes. Notification and bar text are configurable and can be disabled.
# Mangowm Keymode
Mangowm Keymode adds a bar widget that displays MangoWC's current keymode and
can notify you whenever the active keymode changes.
## Plugin
| Field | Value |
| --- | --- |
| ID | `gambled23/mangowm-keymode` |
| Entry | Bar widget: `mangowm-keymode` |
## Requirements
This plugin requires MangoWC and its `mmsg` command on `PATH`. It listens to
`mmsg watch keymode` for live keymode changes.
## Usage
Add the `mangowm-keymode` widget from Noctalia's widget picker. It updates as
MangoWC changes keymode; click it to show the current keymode in a notification.
## Settings
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `show_text` | `bool` | `true` | Shows the current keymode beside the widget glyph. |
| `notify_change` | `bool` | `true` | Sends a notification when the keymode changes. |
+39 -16
View File
@@ -1,32 +1,55 @@
# Mini Docker
Mini Docker is a Noctalia v5 plugin for managing Docker from the shell. The bar widget shows Docker availability and the running-container count. Its management panel can start, stop, restart, and remove containers; run and remove images; and inspect or remove volumes and networks.
Mini Docker manages Docker containers, images, volumes, and networks from
Noctalia. Its bar widget shows Docker availability and the number of running
containers, while its panel provides common management actions.
This Luau implementation migrates the Noctalia v4 Mini Docker plugin originally written by [MannuVilasara](https://github.com/MannuVilasara).
## Plugin
| Field | Value |
| --- | --- |
| ID | `8bury/mini-docker` |
| Entries | Bar widget: `mini-docker`; panel: `manager`; service: `docker-service` |
## Requirements
- Noctalia 5.0.0 or newer
- Docker CLI and a reachable Docker daemon
- Permission for the current user to access Docker
Install the Docker `docker` CLI and make sure your user can connect to the
Docker daemon. Test that `docker info` succeeds without unexpected prompts.
## Usage
Enable `8bury/mini-docker`, then add its `mini-docker` widget to a bar. Click the widget to open the management panel. Right-click it to refresh Docker state immediately.
Add the `mini-docker` widget to a bar. Left-click it to open the management
panel and right-click it to refresh Docker state immediately.
Open the panel directly with:
```sh
noctalia msg panel-toggle 8bury/mini-docker:manager
```
The panel has four tabs:
- Containers: start, stop, restart, or remove a selected container
- Images: run an image with optional name, network, port, and environment variables, or remove an unused image
- Volumes: inspect and remove volumes
- Networks: inspect and remove non-default networks
- **Containers:** start, stop, restart, and remove containers.
- **Images:** run images with an optional name, network, published port, and
environment variables; remove images that are not in use.
- **Volumes:** inspect and remove volumes.
- **Networks:** inspect and remove non-default networks.
Settings control the refresh interval, default run network, running count, icon color, and status indicator.
## Settings
## Security
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `refresh_interval` | `int` | `5` | Seconds between Docker state refreshes. |
| `default_network` | `string` | `bridge` | Network initially selected when running an image. |
| `show_count` | `bool` | `true` | Shows the running-container count in the widget. |
| `glyph_color` | `select` | `on_surface` | Theme color used for the Docker glyph. |
| `status_mode` | `select` | `always` | Shows the status dot always, only while running, or never. |
| `active_color` | `select` | `tertiary` | Status color when a container is running. |
| `inactive_color` | `select` | `error` | Status color when no containers are running. |
Mini Docker invokes only the local `docker` CLI. Subprocess arguments are shell-quoted, and user-entered container names, ports, and environment-variable keys are validated before execution.
## Notes
## License
MIT. Attribution to the original v4 author is retained above.
Mini Docker runs the local Docker CLI with your user's existing daemon access.
Destructive actions require confirmation in the panel. It does not request
elevated privileges, store Docker credentials, mount host paths, or expose
ports unless you explicitly configure a port while running an image.
+63 -14
View File
@@ -1,21 +1,70 @@
# Nix Monitor
![Nix Monitor thumbnail](thumbnail.webp)
Nix Monitor compares the local Nixpkgs revision with a remote branch and shows
NixOS generations, store size, closure size, and update status from the bar.
**Nix Monitor** checks Nixpkgs update by comparing local nix hash and remote Nixpkgs's hash
## Plugin
## Features
- Shows local and remote nixpkgs hash
- Shows NixOS and optionally Home Manager generations
- Shows Nix Store size and closure size
- Customizable clean and update command
| Field | Value |
| --- | --- |
| ID | `avivbintangaringga/nix-monitor` |
| Entries | Bar widget: `nix-monitor`; panel: `panel`; service: `service` |
## Requirements
- Git
- NixOS commands (`nix`, `nixos-rebuild`, `nixos-version`)
- Linux tools (`du`, `cat`, `awk`, `grep`, `tail`, `wc`, `kill`, `pkill`)
- Optionally `home-manager`
## Requirements
This plugin is intended for NixOS. It uses `nix`, `nixos-version`,
`nixos-rebuild`, and `git`, plus the standard commands `du`, `cat`, `awk`,
`grep`, `tail`, `wc`, `kill`, and `pkill`. Home Manager generation information
is shown when `home-manager` is available.
## Usage
Add the `nix-monitor` widget to a bar. Click it to open a panel showing local
and remote Nixpkgs revisions, NixOS and Home Manager generations, store usage,
and update controls.
Open the panel directly with:
```sh
noctalia msg panel-toggle avivbintangaringga/nix-monitor:panel
```
Set `update_command` before using **Update**. **Clean** runs the configured
cleanup command, which defaults to `nix-collect-garbage -d`. Both commands open
in a terminal so you can review their output.
## Settings
Update behavior:
| Setting | Default | Description |
| --- | --- | --- |
| `update_check_interval` | `60` | Minutes between remote revision checks. |
| `update_check_duration_threshold` | `5` | Minutes before an update check is cancelled. |
| `system_stats_check_interval` | `10` | Minutes between store-statistics checks. |
| `system_stats_check_duration_threshold` | `5` | Minutes before a statistics check is cancelled. |
| `show_update_check_notification` | `false` | Notifies when an update check starts and finishes. |
| `show_update_available_notification` | `true` | Notifies when a newer revision is available. |
| `branch` | `nixos-unstable` | Nixpkgs branch compared by the service. |
| `update_command` | *(empty)* | Command launched by the panel's **Update** button. |
| `clean_command` | `nix-collect-garbage -d` | Command launched by **Clean**. |
| `close_on_enter` | `true` | Keeps the command terminal open until Enter is pressed. |
Widget appearance:
| Setting | Default | Description |
| --- | --- | --- |
| `show_text` | `true` | Shows status text beside the glyph. |
| `colorize_text` | `false` | Colors status text using the current state color. |
| `show_glyph` | `true` | Shows the status glyph. |
| `colorize_glyph` | `true` | Colors the glyph using the current state color. |
| `up_to_date_glyph` / `up_to_date_color` | `rosette-discount-check` / `#57ff57` | Up-to-date appearance. |
| `checking_glyph` / `checking_color` | `loader-3` / `#ffeb57` | Checking appearance. |
| `update_available_glyph` / `update_available_color` | `cloud-download` / `#ff5757` | Update-available appearance. |
| `unknown_glyph` / `unknown_color` | `cloud-question` / `on_surface` | Unknown-state appearance. |
## Notes
- You need to add the update command in the setting first
- By default, clean command executes `nix-collect-garbage -d`, you can change it too!
Remote checks contact the configured Nixpkgs Git source. The service writes
temporary revision, size, and PID files under Noctalia's state directory and
terminates overdue helper processes using the declared process tools.
+31 -3
View File
@@ -1,6 +1,34 @@
# PrismLauncher Instances
![prismlauncher instances thumbnail](thumbnail.webp)
**PrismLauncher Instances** is a plugin that adds PrismLauncher instances to the noctalia launcher.
PrismLauncher Instances adds your local Minecraft instances to the Noctalia
launcher so they can be searched and started directly.
## Plugin
| Field | Value |
| --- | --- |
| ID | `radimous/prismlauncher-instances` |
| Entry | Launcher provider: `prismlauncher-instances` |
| Launcher Prefix | `/pl` |
## Requirements
- [prismlauncher](https://github.com/PrismLauncher/PrismLauncher)
Install [PrismLauncher](https://github.com/PrismLauncher/PrismLauncher) and make
sure the `prismlauncher` command is available on `PATH`.
## Usage
Open the Noctalia launcher and type `/pl` to list all detected PrismLauncher
instances. Continue typing to filter by instance name, then activate a result
to launch that instance with `prismlauncher --launch`.
## Settings
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `prism_path` | `string` | `~/.local/share/PrismLauncher` | PrismLauncher data directory containing its configuration and instances. |
## Notes
The provider reads `prismlauncher.cfg`, instance metadata, and local instance
icons from the configured PrismLauncher directory. It does not modify them.
+24 -8
View File
@@ -1,15 +1,31 @@
# Proton Pass
A Noctalia Launcher plugin that integrates with the Proton Pass CLI.
Proton Pass integrates the Proton Pass CLI with the Noctalia launcher, letting
you browse vaults, copy passwords, and display time-based one-time codes.
## Features
## Plugin
- Browse your Proton Pass vaults
- Browse items within each vault
- Copy passwords to the clipboard
- Display TOTP codes as a notification (when available)
| Field | Value |
| --- | --- |
| ID | `lucasoe/proton-pass` |
| Entry | Launcher provider: `proton-pass` |
| Launcher Prefix | `/pass` |
## Requirements
- [proton-pass-cli](https://protonpass.github.io/pass-cli/) installed and available in your `PATH`
- An authenticated Proton Pass CLI session (run `pass-cli login`)
Install `proton-pass-cli` and authenticate it with `pass-cli login` before
using the provider. The `pass-cli` executable must be available on `PATH`.
## Usage
Open the Noctalia launcher and type `/pass` to list Proton Pass vaults. Select
a vault, continue typing to filter its items, and activate an item to copy its
password to the clipboard. When the item has a TOTP secret, the current code is
also displayed in a notification.
## Notes
Vault metadata is cached only in the plugin process for the current session.
Secret values are requested from the authenticated CLI when you activate an
item; passwords are copied to the clipboard and TOTP codes are shown through
Noctalia notifications.
+16 -2
View File
@@ -6,6 +6,20 @@ When the first screencast starts (any app sharing via the portal — Discord,
OBS, browsers, `niri msg action set-dynamic-cast-*`, ...), notification DND is
enabled. When the last screencast stops, notifications come back.
## Plugin
| Field | Value |
| --- | --- |
| ID | `whyoolw/sharednd` |
| Entry | Service: `service` |
## Usage
Enable the plugin in Settings → Plugins while running a niri session. The
headless service starts monitoring screencasts immediately; it has no bar
widget or panel. Start and stop a screen share to verify that Noctalia's DND
state follows the session according to the ownership settings below.
## How it works
- A headless service follows `niri msg -j event-stream` and reacts to
@@ -36,9 +50,9 @@ enabled. When the last screencast stops, notifications come back.
- **Always disable DND after sharing** — force DND off when sharing ends,
regardless of its state before sharing started.
## Requirements & limitations
## Requirements
- Requires **niri** (detection is niri IPC). Detection only starts inside a
- Requires `niri` (detection is niri IPC). Detection only starts inside a
niri session (`NIRI_SOCKET` set and the `niri` binary in `PATH`); on other
compositors the service is inert and spawns no processes.
- Disabling or reloading the plugin mid-share while it owns DND turns DND
+13 -2
View File
@@ -7,14 +7,25 @@ Shelly is a plugin that uses the [Shelly Arch Package Manager](https://github.co
| Field | Value |
| ------- | --------------------------------------- |
| ID | `joshuaslate/shelly` |
| Entries | Service: `poller`; bar widget: `shelly` |
| Entries | Service: `update_poller`; bar widget: `shelly` |
## Requirements
Install the Shelly Arch Package Manager from the [AUR](https://aur.archlinux.org/packages/shelly).
Install the Shelly Arch Package Manager from the [AUR](https://aur.archlinux.org/packages/shelly). The `shelly`
command must be available on `PATH`.
Test that it works by running `shelly check-updates` in a terminal. If it returns a list of packages, then it is working correctly.
## Usage
Add the `shelly` widget from Noctalia's widget picker. It periodically checks
for available Arch package updates and shows their count and names in the bar
tooltip. Click behavior is configurable: open Shelly's graphical interface,
run `shelly upgrade-all` in a terminal, or do nothing.
The `update_poller` service owns the periodic checks and can notify you when
new updates become available.
## Settings
| Setting | Type | Default | Description |
+16
View File
@@ -6,6 +6,22 @@ add tasks with **+**, tick them off (the text is struck through), delete them,
and set each task's priority. The list is kept sorted by priority and stored as
a single JSON file; no external commands are run.
## Plugin
| Field | Value |
| --- | --- |
| ID | `nightwatch75/todo` |
| Entries | Bar widget: `todo`; panel: `panel` |
## Usage
Add the `todo` widget from Noctalia's widget picker and click it to open the
task panel. You can also open the panel directly or bind it in your compositor:
```sh
noctalia msg panel-toggle nightwatch75/todo:panel
```
| Action | Effect |
|-------------------------------|-----------------------------------------------------|
| Left click (bar glyph) | Open/close the To Do panel |
+39 -1
View File
@@ -1 +1,39 @@
Set the fan state on the ZenBook S 16 UM5606 (and related laptops). Requires https://github.com/ThatOneCalculator/asus-5606-fan-state
# ASUS UM5606 Fan State
ASUS UM5606 Fan State shows and changes the firmware fan profile on the
ZenBook S 16 UM5606 and compatible laptops from the bar or Control Center.
## Plugin
| Field | Value |
| --- | --- |
| ID | `thatonecalculator/um5606_fan_state` |
| Entries | Bar widget: `fan_state`; shortcut: `toggle`; service: `service` |
## Requirements
Install the `fan_state` command from
[asus-5606-fan-state](https://github.com/ThatOneCalculator/asus-5606-fan-state)
and verify that `fan_state get --int` works for your hardware.
## Usage
Add the `fan_state` widget to a bar, or add the `toggle` shortcut under
Settings → Control Center shortcuts. Click either entry to cycle through
Standard, Quiet, High-Performance, and Full fan profiles.
The headless `service` polls the current profile and owns all calls to the
hardware helper. Both user-facing entries become disabled or show
**Unavailable** when the helper cannot report a valid state.
## Settings
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `show_label` | `bool` | `false` | Shows the current profile name beside the bar glyph. |
| `poll_interval` | `int` | `2000` | Hardware polling interval in milliseconds, from 250 to 60000. |
## Notes
This plugin changes a hardware fan-control setting by running `fan_state set`.
Only use it on hardware supported by the helper project.
+29 -2
View File
@@ -1,3 +1,30 @@
# Upbeat
![upbeat thumbnail](thumbnail.webp)
**Upbeat** is a bar widget plugin written for Noctalia (v5) that displays the current Internet Time. Internet time is a subversive, yet extremely accurate and location agnostic way of telling time created in the 90s for the purpose of communicating in the future. Also it's kinda funny.
Upbeat adds a bar widget that displays Swatch Internet Time, a timezone-neutral
decimal time system measured in beats and centibeats.
## Plugin
| Field | Value |
| --- | --- |
| ID | `neuro/upbeat` |
| Entry | Bar widget: `upbeat` |
## Usage
Add the `upbeat` widget from Noctalia's widget picker. The widget displays the
current Internet Time at UTC+1; hover it to see conventional local time in the
configured 12- or 24-hour format.
## Settings
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `show_centibeats` | `bool` | `true` | Shows two centibeat digits after the decimal point. |
| `beat_display` | `bool` | `false` | Appends the `.beats` label to the value. |
| `time_format_toggle` | `bool` | `false` | Uses 24-hour time instead of 12-hour time in the tooltip. |
## Notes
Internet Time is computed locally and does not contact a network service. The
widget updates more frequently when centibeats are displayed.