diff --git a/linux/blitztext/__init__.py b/linux/blitztext/__init__.py index 1ad49a4..a408087 100644 --- a/linux/blitztext/__init__.py +++ b/linux/blitztext/__init__.py @@ -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" diff --git a/linux/blitztext/config.py b/linux/blitztext/config.py index 6df20bc..37862bb 100644 --- a/linux/blitztext/config.py +++ b/linux/blitztext/config.py @@ -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, diff --git a/linux/blitztext/gtksettings.py b/linux/blitztext/gtksettings.py index 0b3e436..c6e1e74 100644 --- a/linux/blitztext/gtksettings.py +++ b/linux/blitztext/gtksettings.py @@ -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 ""