docs: standardize and validate plugin READMEs
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. -->
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
# mangowm-keymode
|
||||

|
||||
**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
@@ -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.
|
||||
|
||||
+62
-13
@@ -1,21 +1,70 @@
|
||||
# Nix Monitor
|
||||
|
||||

|
||||
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`
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,6 +1,34 @@
|
||||
# PrismLauncher Instances
|
||||

|
||||
**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
@@ -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
@@ -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
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
@@ -1,3 +1,30 @@
|
||||
# Upbeat
|
||||

|
||||
**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.
|
||||
|
||||
Reference in New Issue
Block a user