feat: wakeword server preset dropdown in Input tab (v2.02.03)

Adds a "Server preset" combo in the Hands-free wakeword card that lists
all configured wakeword engines by name. Selecting one auto-fills the
URI and model fields and re-probes the connection. Selection persists
via wakeword_active in config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-06-09 21:01:57 +02:00
parent 48014ae662
commit 6fb41d5a9b
3 changed files with 32 additions and 1 deletions

View File

@ -6,4 +6,4 @@ counterpart to the macOS Blitztext menu bar app: it runs natively on the host
(not in a container) so it can type into any application via xdotool.
"""
__version__ = "2.02.02"
__version__ = "2.02.03"

View File

@ -104,6 +104,7 @@ class Config:
llm_active: str = ""
# wakeword
wakeword_enabled: bool = False
wakeword_active: str = "" # name of selected WakewordEngine preset, "" = custom
wakeword_uri: str = "tcp://127.0.0.1:10400"
wakeword_model: str = "okay_computer"
wakeword_sound_detected: str = "" # WAV played when the wakeword fires (speak now)
@ -232,6 +233,7 @@ def load(path: Path = CONFIG_PATH) -> Config:
sound_before=snd.get("before", ""),
sound_after=snd.get("after", ""),
wakeword_enabled=bool(ww.get("enabled", False)),
wakeword_active=ww.get("active", ""),
wakeword_uri=ww.get("uri", "tcp://127.0.0.1:10400"),
wakeword_model=ww.get("model", "okay_computer"),
wakeword_sound_detected=ww.get("sound_detected", ""),
@ -368,6 +370,7 @@ def save(cfg: Config, path: Path = CONFIG_PATH) -> None:
},
"wakeword": {
"enabled": cfg.wakeword_enabled,
"active": cfg.wakeword_active,
"uri": cfg.wakeword_uri,
"model": cfg.wakeword_model,
"sound_detected": cfg.wakeword_sound_detected,

View File

@ -1641,6 +1641,18 @@ notebook.bt-nb tab:checked label {
self.ww_enabled = Gtk.Switch(); self.ww_enabled.set_active(self.cfg.wakeword_enabled)
_switch_row(ww_card, "Enable wakeword", self.ww_enabled, width=LW,
description="Start dictation with a spoken keyword via an external openWakeWord server.")
# ── Server preset dropdown ────────────────────────────────────────────
self.ww_combo = Gtk.ComboBoxText()
self.ww_combo.append_text("(custom)")
for e in self.cfg.wakeword_engines:
self.ww_combo.append_text(e.name)
active_idx = next((i + 1 for i, e in enumerate(self.cfg.wakeword_engines)
if e.name == self.cfg.wakeword_active), 0)
self.ww_combo.set_active(active_idx)
self.ww_combo.set_tooltip_text("Pick a saved wakeword server preset, or use (custom) to enter a URI manually.")
_labeled(ww_card, "Server preset", self.ww_combo, width=LW,
tooltip="Select from wakeword server presets defined in the benchmark list, or (custom) to type a URI directly.")
self.ww_dot = Gtk.Label(); self.ww_dot.set_markup(_dot(GREY))
self.ww_dot.set_tooltip_text("openWakeWord server: green = reachable, red = unreachable")
self.ww_uri = _url_field_lb(ww_card, "Wakeword server", "tcp://127.0.0.1:10400",
@ -1651,6 +1663,7 @@ notebook.bt-nb tab:checked label {
self.ww_model = _labeled(ww_card, "Model name", _model_combo("Search models…"), width=LW,
tooltip="Which wake model to listen for (e.g. computer, okay_computer). Press ⟳ on the URI field to load models from the server.")
_fill_combo(self.ww_model, [], self.cfg.wakeword_model)
self.ww_combo.connect("changed", self._ww_preset_changed)
self.ww_mic_level = Gtk.LevelBar()
self.ww_mic_level.set_min_value(0); self.ww_mic_level.set_max_value(1)
@ -1798,6 +1811,17 @@ notebook.bt-nb tab:checked label {
if self._meter is not None:
self._meter.stop(); self._meter = None
def _ww_preset_changed(self, combo) -> None:
idx = combo.get_active()
if idx <= 0 or idx - 1 >= len(self.cfg.wakeword_engines):
return # (custom) selected — leave fields as-is
e = self.cfg.wakeword_engines[idx - 1]
self.cfg.wakeword_active = e.name
self.ww_uri.set_text(e.uri)
_fill_combo(self.ww_model, [], e.model)
self._probe_dot(self.ww_dot, e.uri, 10400)
self._ww_load()
def _ww_load(self) -> None:
self._probe_dot(self.ww_dot, self.ww_uri.get_text(), 10400)
@ -2466,6 +2490,10 @@ notebook.bt-nb tab:checked label {
c.sound_before = self.snd_before.get_filename() or ""
c.sound_after = self.snd_after.get_filename() or ""
c.wakeword_enabled = self.ww_enabled.get_active()
preset_idx = self.ww_combo.get_active()
c.wakeword_active = (self.cfg.wakeword_engines[preset_idx - 1].name
if preset_idx > 0 and preset_idx - 1 < len(self.cfg.wakeword_engines)
else "")
c.wakeword_uri = self.ww_uri.get_text().strip()
c.wakeword_model = _combo_text(self.ww_model)
c.wakeword_sound_detected = self.ww_snd_detected.get_filename() or ""