feat(voxtype): init (#297)

This commit is contained in:
Gabe Dunn
2026-08-08 11:07:55 -04:00
committed by GitHub
parent eba378b319
commit fe464e7301
5 changed files with 392 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
# Voxtype
Voxtype adds a bar indicator and recording controls for Voxtype. It uses
configurable Noctalia glyphs and colors while preserving Voxtype's own status
tooltip.
## Plugin
| Field | Value |
| --- | --- |
| ID | `gabedunn/voxtype` |
| Entries | Bar widget: `status` |
## Requirements
Install [`voxtype`](https://github.com/peteonrails/voxtype) and make sure the
`voxtype` command is available on `PATH`. Configure Voxtype and its recording
hotkey before using this widget.
## Usage
Enable the plugin in Settings → Plugins, then add the `status` widget to your
bar in Settings → Bar.
The widget follows Voxtype's live status and updates as soon as it changes:
| State | Default glyph | Default color | Tooltip |
| --- | --- | --- | --- |
| `idle` | `microphone-2-off` | `on_surface` | Voxtype ready - hold hotkey to record |
| `streaming` | `microphone-2` | `error` | Streaming live... |
| `recording` | `microphone-2` | `error` | Recording... |
| `transcribing` | `loader` | `primary` | Transcribing... |
| `stopped` | `microphone-2-off` | `error` | Voxtype not running |
Hover over the widget to see the tooltip supplied by Voxtype. By default the
bar shows only the glyph; enable **Show status text** to display Voxtype's
`alt` field beside it. Enable **Extended status** to request the model, device,
and backend. Voxtype always adds all three to the expanded tooltip; nested
settings let you choose which fields also appear on the bar.
Default pointer actions:
- **Left-click** runs `voxtype record toggle` to start or stop recording.
- **Right-click** runs `voxtype record stop` to stop recording.
- **Middle-click** opens the widget settings using Noctalia's standard action.
You can override these gesture bindings for each widget instance in the bar
settings.
## Settings
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `show_alt` | `bool` | `false` | Show the current state (`idle`, `streaming`, `recording`, `transcribing`, or `stopped`) beside the glyph. |
| `show_extended` | `bool` | `false` | Enable extended output. The tooltip includes model, device, and backend. |
| `show_model` | `bool` | `true` | Show the model on the bar when extended status is enabled. |
| `show_device` | `bool` | `true` | Show the audio device on the bar when extended status is enabled. |
| `show_backend` | `bool` | `true` | Show the backend on the bar when extended status is enabled. |
| `idle_glyph` | `glyph` | `microphone-2-off` | Glyph shown in the idle state. |
| `idle_color` | `color` | `on_surface` | Glyph color in the idle state. |
| `streaming_glyph` | `glyph` | `microphone-2` | Glyph shown in the streaming state. |
| `streaming_color` | `color` | `error` | Glyph color in the streaming state. |
| `recording_glyph` | `glyph` | `microphone-2` | Glyph shown in the recording state. |
| `recording_color` | `color` | `error` | Glyph color in the recording state. |
| `transcribing_glyph` | `glyph` | `loader` | Glyph shown while Voxtype is transcribing recorded audio. |
| `transcribing_color` | `color` | `primary` | Glyph color in the transcribing state. |
| `stopped_glyph` | `glyph` | `microphone-2-off` | Glyph shown while Voxtype is not running. |
| `stopped_color` | `color` | `error` | Glyph color in the stopped state. |
## Notes
For each bar instance, the widget starts a managed shell loop that runs one
`voxtype status --follow --format json` process at a time, adding `--extended`
when extended status is enabled. If the status process exits, the loop waits two
seconds before restarting it. Noctalia stops the process group when the widget
unloads. Each JSON line replaces the current glyph, color, optional status text,
and tooltip; the emoji in Voxtype's `text` field is not rendered.
The plugin makes no network requests and does not directly read or write files.
Its click actions invoke Voxtype, which records and processes audio according to
your Voxtype configuration.
+126
View File
@@ -0,0 +1,126 @@
id = "gabedunn/voxtype"
name = "Voxtype"
version = "1.0.0"
plugin_api = 14
author = "Gabe Dunn"
license = "MIT"
icon = "microphone-2"
description = "Shows and controls Voxtype's live dictation status from the bar."
dependencies = ["voxtype"]
tags = ["audio", "bar", "indicator", "recording", "utility"]
[[widget]]
id = "status"
entry = "widget.luau"
[widget.actions]
left = "exec voxtype record toggle"
right = "exec voxtype record stop"
[[widget.setting]]
key = "show_alt"
type = "bool"
label_key = "settings.show_alt.label"
description_key = "settings.show_alt.description"
default = false
[[widget.setting]]
key = "show_extended"
type = "bool"
label_key = "settings.show_extended.label"
description_key = "settings.show_extended.description"
default = false
[[widget.setting]]
key = "show_model"
type = "bool"
label_key = "settings.show_model.label"
description_key = "settings.show_model.description"
default = true
visible_when = { key = "show_extended", values = ["true"] }
[[widget.setting]]
key = "show_device"
type = "bool"
label_key = "settings.show_device.label"
description_key = "settings.show_device.description"
default = true
visible_when = { key = "show_extended", values = ["true"] }
[[widget.setting]]
key = "show_backend"
type = "bool"
label_key = "settings.show_backend.label"
description_key = "settings.show_backend.description"
default = true
visible_when = { key = "show_extended", values = ["true"] }
[[widget.setting]]
key = "idle_glyph"
type = "glyph"
label_key = "settings.idle_glyph.label"
description_key = "settings.idle_glyph.description"
default = "microphone-2-off"
[[widget.setting]]
key = "idle_color"
type = "color"
label_key = "settings.idle_color.label"
description_key = "settings.idle_color.description"
default = "on_surface"
[[widget.setting]]
key = "streaming_glyph"
type = "glyph"
label_key = "settings.streaming_glyph.label"
description_key = "settings.streaming_glyph.description"
default = "microphone-2"
[[widget.setting]]
key = "streaming_color"
type = "color"
label_key = "settings.streaming_color.label"
description_key = "settings.streaming_color.description"
default = "error"
[[widget.setting]]
key = "recording_glyph"
type = "glyph"
label_key = "settings.recording_glyph.label"
description_key = "settings.recording_glyph.description"
default = "microphone-2"
[[widget.setting]]
key = "recording_color"
type = "color"
label_key = "settings.recording_color.label"
description_key = "settings.recording_color.description"
default = "error"
[[widget.setting]]
key = "transcribing_glyph"
type = "glyph"
label_key = "settings.transcribing_glyph.label"
description_key = "settings.transcribing_glyph.description"
default = "loader"
[[widget.setting]]
key = "transcribing_color"
type = "color"
label_key = "settings.transcribing_color.label"
description_key = "settings.transcribing_color.description"
default = "primary"
[[widget.setting]]
key = "stopped_glyph"
type = "glyph"
label_key = "settings.stopped_glyph.label"
description_key = "settings.stopped_glyph.description"
default = "microphone-2-off"
[[widget.setting]]
key = "stopped_color"
type = "color"
label_key = "settings.stopped_color.label"
description_key = "settings.stopped_color.description"
default = "error"
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+72
View File
@@ -0,0 +1,72 @@
{
"settings": {
"idle_color": {
"description": "Color of the icon while Voxtype is ready and waiting.",
"label": "Idle icon color"
},
"idle_glyph": {
"description": "Noctalia glyph shown while Voxtype is ready and waiting.",
"label": "Idle icon"
},
"recording_color": {
"description": "Color of the icon while Voxtype is recording.",
"label": "Recording icon color"
},
"recording_glyph": {
"description": "Noctalia glyph shown while Voxtype is recording.",
"label": "Recording icon"
},
"show_alt": {
"description": "Show Voxtype's current state (idle, streaming, recording, transcribing, or stopped) next to the icon.",
"label": "Show status text"
},
"show_backend": {
"description": "Show the active audio backend on the bar.",
"label": "Show backend on bar"
},
"show_device": {
"description": "Show the active audio device on the bar.",
"label": "Show device on bar"
},
"show_extended": {
"description": "Request Voxtype's extended status. The tooltip includes model, device, and backend; the settings below control which fields also appear on the bar.",
"label": "Extended status"
},
"show_model": {
"description": "Show the active transcription model on the bar.",
"label": "Show model on bar"
},
"streaming_color": {
"description": "Color of the icon while Voxtype is streaming live audio.",
"label": "Streaming icon color"
},
"streaming_glyph": {
"description": "Noctalia glyph shown while Voxtype is streaming live audio.",
"label": "Streaming icon"
},
"stopped_color": {
"description": "Color of the icon while Voxtype is not running.",
"label": "Stopped icon color"
},
"stopped_glyph": {
"description": "Noctalia glyph shown while Voxtype is not running.",
"label": "Stopped icon"
},
"transcribing_color": {
"description": "Color of the icon while Voxtype is transcribing recorded audio.",
"label": "Transcribing icon color"
},
"transcribing_glyph": {
"description": "Noctalia glyph shown while Voxtype is transcribing recorded audio.",
"label": "Transcribing icon"
}
},
"widget": {
"backend": "Backend: {value}",
"device": "Device: {value}",
"model": "Model: {value}",
"stream_error": "Could not start the Voxtype status stream",
"unavailable": "Voxtype is not installed or is not available on PATH",
"waiting": "Waiting for Voxtype status…"
}
}
+113
View File
@@ -0,0 +1,113 @@
--!nonstrict
-- Voxtype bar widget.
--
-- `voxtype status --follow --format json` emits one JSON object immediately,
-- then another whenever the dictation state changes. The command's emoji
-- `text` value is intentionally ignored so Noctalia can render a themeable
-- glyph instead. Voxtype's `--extended` output always expands the tooltip with
-- all three fields; the nested settings choose which fields also appear on the
-- bar.
local SHOW_ALT = noctalia.getConfig("show_alt") == true
local SHOW_EXTENDED = noctalia.getConfig("show_extended") == true
local EXTENDED_FIELDS = {
{ name = "model", visible = noctalia.getConfig("show_model") ~= false },
{ name = "device", visible = noctalia.getConfig("show_device") ~= false },
{ name = "backend", visible = noctalia.getConfig("show_backend") ~= false },
}
local STATES = {
idle = {
glyph = noctalia.getConfig("idle_glyph") or "microphone-2-off",
color = noctalia.getConfig("idle_color") or "on_surface",
},
streaming = {
glyph = noctalia.getConfig("streaming_glyph") or "microphone-2",
color = noctalia.getConfig("streaming_color") or "error",
},
recording = {
glyph = noctalia.getConfig("recording_glyph") or "microphone-2",
color = noctalia.getConfig("recording_color") or "error",
},
transcribing = {
glyph = noctalia.getConfig("transcribing_glyph") or "loader",
color = noctalia.getConfig("transcribing_color") or "primary",
},
stopped = {
glyph = noctalia.getConfig("stopped_glyph") or "microphone-2-off",
color = noctalia.getConfig("stopped_color") or "error",
},
}
local function render(status)
if type(status) ~= "table" then
return
end
local state = STATES[status.class]
if state == nil then
noctalia.log("voxtype: ignored unknown status class " .. tostring(status.class))
return
end
barWidget.setGlyph(state.glyph)
barWidget.setGlyphColor(state.color)
local text = {}
if SHOW_ALT and type(status.alt) == "string" then
table.insert(text, status.alt)
end
if SHOW_EXTENDED then
for _, field in ipairs(EXTENDED_FIELDS) do
local value = status[field.name]
if field.visible and type(value) == "string" and value ~= "" then
table.insert(text, noctalia.tr("widget." .. field.name, { value = value }))
end
end
end
barWidget.setText(table.concat(text, " · "))
if type(status.tooltip) == "string" then
barWidget.setTooltip(status.tooltip)
else
barWidget.clearTooltip()
end
end
local function onStatusLine(line)
local ok, status = pcall(noctalia.json.decode, line)
if not ok or type(status) ~= "table" then
noctalia.log("voxtype: ignored invalid status JSON")
return
end
render(status)
end
-- Use the configured idle appearance while waiting for the command's initial
-- status line.
barWidget.setGlyph(STATES.idle.glyph)
barWidget.setGlyphColor(STATES.idle.color)
barWidget.setText("")
barWidget.setTooltip(noctalia.tr("widget.waiting"))
if not noctalia.commandExists("voxtype") then
barWidget.setGlyph("microphone-off")
barWidget.setGlyphColor("error")
barWidget.setTooltip(noctalia.tr("widget.unavailable"))
else
local command = "voxtype status --follow --format json"
if SHOW_EXTENDED then
command = command .. " --extended"
end
-- runStream has no exit callback or restart policy. Keep the retry lifecycle
-- in one managed shell process: only one status command runs at a time, a
-- failure waits before restarting, and the loop ends once its host is gone.
local retryLoop = 'P=$PPID; while kill -0 "$P" 2>/dev/null; do '
.. command .. "; sleep 2; done"
if not noctalia.runStream(retryLoop, onStatusLine) then
barWidget.setGlyph("microphone-off")
barWidget.setGlyphColor("error")
barWidget.setTooltip(noctalia.tr("widget.stream_error"))
end
end