feat(voxtype): init (#297)
This commit is contained in:
@@ -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.
|
||||
@@ -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 |
@@ -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…"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user