From 681e8ee0576211ce3d1f6bb1de8cb2244bfc20bd Mon Sep 17 00:00:00 2001 From: goodroot <9484709+goodroot@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:16:39 -0700 Subject: [PATCH] feat: add noctwhspr plugin --- noctwhspr/README.md | 72 +++++++++++++++++++ noctwhspr/plugin.toml | 21 ++++++ noctwhspr/thumbnail.webp | Bin 0 -> 25222 bytes noctwhspr/translations/en.json | 5 ++ noctwhspr/widget.luau | 125 +++++++++++++++++++++++++++++++++ 5 files changed, 223 insertions(+) create mode 100644 noctwhspr/README.md create mode 100644 noctwhspr/plugin.toml create mode 100644 noctwhspr/thumbnail.webp create mode 100644 noctwhspr/translations/en.json create mode 100644 noctwhspr/widget.luau diff --git a/noctwhspr/README.md b/noctwhspr/README.md new file mode 100644 index 0000000..fef78f3 --- /dev/null +++ b/noctwhspr/README.md @@ -0,0 +1,72 @@ +# noctwhspr + +Noctalia companion for [hyprwhspr](https://github.com/goodroot/hyprwhspr) — +native speech-to-text for Linux. hyprwhspr is fast, accurate, private, +system-wide dictation: press a hotkey, talk, and your words land in whatever +you were typing — transcribed by local in-memory models (Whisper, Parakeet) +that never leave your machine, or by top cloud APIs if you point it there. +Its visuals even theme themselves to match your Noctalia theme. + +This plugin puts hyprwhspr on your bar: the widget shows the dictation state +at a glance, left-click starts or stops a recording, and right-click restarts +the hyprwhspr service if it ever gets stuck. + +## Plugin + +| Field | Value | +| --- | --- | +| ID | `goodroot/noctwhspr` | +| Entries | Bar widget: `status` | + +## Requirements + +Install [`hyprwhspr`](https://github.com/goodroot/hyprwhspr) and set it up so +its `hyprwhspr.service` systemd user service exists. The widget drives +hyprwhspr's own tray script (`hyprwhspr-tray.sh`), which it looks up under the +install root — `/usr/lib/hyprwhspr` for the Arch/AUR package. Nothing else is +needed; the widget has no network access of its own. + +## Usage + +Enable the plugin in Settings → Plugins, then add the `status` widget to your +bar in Settings → Bar. + +The glyph tracks the dictation state reported by hyprwhspr: + +| Glyph | State | +| --- | --- | +| Filled record dot (error color) | Recording — dictation is capturing audio | +| Microphone (primary color) | Ready — service is up, waiting for a hotkey or click | +| Zzz | Model unloaded — first recording will load it | +| Microphone off | Service stopped | +| Warning triangle | Error — the tooltip explains what is wrong | + +Interactions: + +- **Left-click** — toggle recording (same as the hyprwhspr hotkey). If the + service is stopped, this starts it first. +- **Right-click** — restart the `hyprwhspr.service` systemd user service. +- **Hover** — the tooltip shows the detailed status line, including error + reasons when something is wrong. + +## Settings + +| Setting | Type | Default | Description | +| --- | --- | --- | --- | +| `root` | `string` | `/usr/lib/hyprwhspr` | Directory hyprwhspr is installed under. Leave at the default unless hyprwhspr lives at a non-standard prefix; `HYPRWHSPR_ROOT` in Noctalia's environment is also honored. | + +## Notes + +- The widget is a thin shim over hyprwhspr's tray script — the same script + that backs its Waybar module — so state detection stays in one place. It + polls `/config/hyprland/hyprwhspr-tray.sh status` every 2 seconds and + renders the returned JSON. +- Spawned processes: the tray script on every poll and click; the script in + turn queries `systemctl --user` and hyprwhspr's runtime state files. + Right-click runs `systemctl --user restart hyprwhspr.service` through the + script. No network calls and no filesystem writes are made by the plugin + itself. +- If the widget shows "hyprwhspr not found", hyprwhspr is not installed at + the configured root — point the `root` setting at your install prefix. +- Works on any compositor Noctalia runs on; the `hyprland` in the script path + is historical, hyprwhspr itself supports Hyprland, Niri and friends. diff --git a/noctwhspr/plugin.toml b/noctwhspr/plugin.toml new file mode 100644 index 0000000..ebfe1f6 --- /dev/null +++ b/noctwhspr/plugin.toml @@ -0,0 +1,21 @@ +id = "goodroot/noctwhspr" +name = "noctwhspr" +version = "1.0.0" +plugin_api = 3 +author = "goodroot" +license = "MIT" +icon = "microphone" +description = "Noctalia companion for hyprwhspr: shows dictation state, click to record, right-click to restart." +dependencies = ["hyprwhspr"] +tags = ["audio", "bar", "recording", "utility"] + +[[widget]] +id = "status" +entry = "widget.luau" + + [[widget.setting]] + key = "root" + type = "string" + label_key = "settings.root.label" + description_key = "settings.root.description" + default = "/usr/lib/hyprwhspr" diff --git a/noctwhspr/thumbnail.webp b/noctwhspr/thumbnail.webp new file mode 100644 index 0000000000000000000000000000000000000000..f0a11ce8327310bc454124edf9ce7a1facaf8aa1 GIT binary patch literal 25222 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 KYL*KF0|Nk9b^d1n literal 0 HcmV?d00001 diff --git a/noctwhspr/translations/en.json b/noctwhspr/translations/en.json new file mode 100644 index 0000000..a141050 --- /dev/null +++ b/noctwhspr/translations/en.json @@ -0,0 +1,5 @@ +{ + "title": "noctwhspr", + "settings.root.label": "hyprwhspr install root", + "settings.root.description": "Directory hyprwhspr is installed under. Leave at the default unless hyprwhspr lives at a non-standard prefix." +} diff --git a/noctwhspr/widget.luau b/noctwhspr/widget.luau new file mode 100644 index 0000000..f0c326e --- /dev/null +++ b/noctwhspr/widget.luau @@ -0,0 +1,125 @@ +--!nonstrict +-- hyprwhspr bar widget for Noctalia. +-- +-- Thin shim over the existing tray script (single source of truth for state +-- detection, shared with the Waybar module): polls `hyprwhspr-tray.sh status` +-- and renders its JSON (class + tooltip) as a glyph. Clicks invoke the same +-- script actions the Waybar module binds. + +local TRAY_REL = "/config/hyprland/hyprwhspr-tray.sh" + +-- state class (from the tray script's JSON) -> glyph + palette color +local STATES = { + recording = { glyph = "player-record-filled", color = "error" }, + ready = { glyph = "microphone", color = "primary" }, + unloaded = { glyph = "zzz", color = "outline" }, + stopped = { glyph = "microphone-off", color = "outline" }, + error = { glyph = "alert-triangle", color = "error" }, +} + +local function applyState(state, tooltip) + barWidget.setGlyph(state.glyph) + barWidget.setGlyphColor(state.color) + if tooltip ~= nil and tooltip ~= "" then + barWidget.setTooltip(tooltip) + end +end + +local function applyError(tooltip) + applyState(STATES.error, tooltip) +end + +-- Resolve the tray script lazily and cache the hit. Noctalia's process is +-- spawned by the compositor, so HYPRWHSPR_ROOT is usually absent from its +-- environment — the widget "root" setting is the override for non-standard +-- install prefixes. +local trayCached = nil +local function trayPath() + if trayCached ~= nil and noctalia.fileExists(trayCached) then + return trayCached + end + trayCached = nil + local candidates = {} + local cfg = noctalia.getConfig("root") + if type(cfg) == "string" and cfg ~= "" then + table.insert(candidates, cfg) + end + local env = noctalia.getenv("HYPRWHSPR_ROOT") + if env ~= nil and env ~= "" then + table.insert(candidates, env) + end + table.insert(candidates, "/usr/lib/hyprwhspr") + for _, root in ipairs(candidates) do + local path = root .. TRAY_REL + if noctalia.fileExists(path) then + trayCached = path + return path + end + end + return nil +end + +local function trayCommand(action) + local path = trayPath() + if path == nil then + return nil + end + -- Single-quote the path: install roots may contain spaces. + return "'" .. path .. "' " .. action +end + +local function render(json) + if type(json) ~= "string" or json == "" then + applyError("hyprwhspr: tray script returned no status") + return + end + local class = json:match('"class":"([^"]*)"') or "error" + local tooltip = json:match('"tooltip":"([^"]*)"') or "" + + -- Strip the Waybar cache-buster line, unescape \n + tooltip = tooltip:gsub("\\n_ts:%d+", "") + tooltip = tooltip:gsub("\\n", "\n") + + applyState(STATES[class] or STATES.error, tooltip) +end + +local function refresh() + local cmd = trayCommand("status") + if cmd == nil then + applyError("hyprwhspr not found\nInstall hyprwhspr or set this widget's root setting") + return + end + noctalia.runAsync(cmd, function(result) + if type(result) ~= "table" then + applyError("hyprwhspr: status query failed") + return + end + render(result.stdout) + end) +end + +function update() + -- The tray script re-derives state via many subprocesses per call; 2s is + -- responsive enough (the recording overlay gives immediate feedback). + noctalia.setUpdateInterval(2000) + refresh() +end + +function onClick() + local cmd = trayCommand("record") + if cmd ~= nil then + noctalia.runAsync(cmd, function() refresh() end) + end +end + +function onRightClick() + local cmd = trayCommand("restart") + if cmd ~= nil then + noctalia.runAsync(cmd, function() refresh() end) + end +end + +-- Honest placeholder until the first poll lands. +barWidget.setGlyph("microphone") +barWidget.setGlyphColor("outline") +barWidget.setTooltip("hyprwhspr: waiting for status…")