public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/ibus-speech-to-text] rawhide: update to release 1.1.0
@ 2026-09-21 21:59 matiwari
  0 siblings, 0 replies; only message in thread
From: matiwari @ 2026-09-21 21:59 UTC (permalink / raw)
  To: git-commits

A new commit has been pushed.

Repo   : rpms/ibus-speech-to-text
Branch : rawhide
Commit : 38f002b367ae842015cc569d091ed0ae9bf17f62
Author : matiwari <matiwari@redhat.com>
Date   : 2026-09-22T02:59:29+05:30
Stats  : +8/-1469 in 4 file(s)
URL    : https://src.fedoraproject.org/rpms/ibus-speech-to-text/c/38f002b367ae842015cc569d091ed0ae9bf17f62?branch=rawhide

Log:
update to release 1.1.0

---
diff --git a/.gitignore b/.gitignore
index 98a27e3..21723c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
 /0.8.0.tar.gz
 /0.9.0.tar.gz
 /1.0.0.tar.gz
+/1.1.0.tar.gz

diff --git a/ibus-speech-to-text.spec b/ibus-speech-to-text.spec
index f856071..9d66ce8 100644
--- a/ibus-speech-to-text.spec
+++ b/ibus-speech-to-text.spec
@@ -1,13 +1,12 @@
 %global debug_package %{nil}
 Name:   ibus-speech-to-text
-Version:  1.0.0
+Version:  1.1.0
 Release:  1%{?dist}
 Summary:  A speech to text IBus Input Method using VOSK
 ExcludeArch: %{ix86}
 License:  GPL-3.0-or-later
 URL:     https://github.com/Manish7093/IBus-Speech-To-Text
 Source0: https://github.com/Manish7093/IBus-Speech-To-Text/archive/refs/tags/%{version}.tar.gz
-Patch0: ibus-stt-1_0_0-backend-notify.patch
 
 BuildRequires:  meson
 BuildRequires:  python3-devel
@@ -27,6 +26,7 @@ Requires:    gst-vosk >= 0.3.0
 Requires:    gtk4
 Requires:    dconf
 Requires:    python3-pywhispercpp
+Requires:    libadwaita
 
 %description
 A speech to text IBus Input Method using VOSK and WhisperCpp
@@ -34,7 +34,6 @@ which can be used to dictate text to any application
 
 %prep
 %setup -q -n IBus-Speech-To-Text-%{version}
-%patch 0 -p1 -b .orig~
 
 %build
 %meson
@@ -57,6 +56,10 @@ desktop-file-validate %{buildroot}/%{_datadir}/applications/ibus-setup-stt.deskt
 %{_datadir}/glib-2.0/schemas/org.freedesktop.ibus.engine.stt.gschema.xml
 
 %changelog
+* Tue Sep 22 2026 Manish Tiwari <matiwari@redhat.com> 1.1.0-1
+- Update to release 1.1.0
+- https://github.com/Manish7093/IBus-Speech-To-Text/releases/tag/1.1.0
+
 * Fri Aug 7 2026 Manish Tiwari <matiwari@redhat.com> 1.0.0-1
 - Update to release 1.0.0
 - Add ibus-stt-1_0_0-backend-notify.patch to support backend notification dialog

diff --git a/ibus-stt-1_0_0-backend-notify.patch b/ibus-stt-1_0_0-backend-notify.patch
deleted file mode 100644
index f1d9c28..0000000
--- a/ibus-stt-1_0_0-backend-notify.patch
+++ /dev/null
@@ -1,1465 +0,0 @@
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/meson.build IBus-Speech-To-Text-1.0.0/engine/meson.build
---- IBus-Speech-To-Text-1.0.0.orig/engine/meson.build	2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/meson.build	2026-08-07 01:42:43.021671212 +0530
-@@ -18,6 +18,7 @@
-     'sttshortcutdialog.py',
-     'sttutterancerow.py',
-     'sttvad.py',
-+    'sttbackenddeps.py',
-     'sttmodelchooserdialog.py',
-     'sttvoskmodelmanagers.py',
-     'sttwhispermodelmanagers.py',
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttbackenddeps.py IBus-Speech-To-Text-1.0.0/engine/sttbackenddeps.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttbackenddeps.py	1970-01-01 05:30:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttbackenddeps.py	2026-08-07 01:44:45.299598828 +0530
-@@ -0,0 +1,173 @@
-+import importlib
-+import importlib.machinery
-+import importlib.util
-+import logging
-+import sys
-+
-+LOG_MSG = logging.getLogger()
-+
-+class STTBackendDependency:
-+
-+    def __init__(self, module, distribution, extras=None):
-+        self.module = module
-+        self.distribution = distribution
-+        self.extras = tuple(extras) if extras is not None else ()
-+
-+    @property
-+    def requirement(self):
-+        if not self.extras:
-+            return self.distribution
-+        return "%s[%s]" % (self.distribution, ",".join(self.extras))
-+
-+    @property
-+    def pip_argument(self):
-+        if not self.extras:
-+            return self.distribution
-+        return "'%s'" % self.requirement
-+
-+    def available(self):
-+        #True when the module can be found on sys.path right now
-+        try:
-+            spec = importlib.machinery.PathFinder.find_spec(self.module,
-+                                                            sys.path)
-+        except (ImportError, ValueError, TypeError, AttributeError) as error:
-+            LOG_MSG.debug("cannot look up module %s (%s)", self.module, error)
-+            return False
-+
-+        return spec is not None
-+
-+    def __repr__(self):
-+        return "<STTBackendDependency %s>" % self.requirement
-+
-+# NOTE: keep the keys in sync with the "backend" GSettings key and with the
-+# check buttons of sttconfigdialog.ui.
-+_BACKENDS = {
-+    "vosk": {
-+        "name": "Vosk",
-+        "dependencies": (),
-+        "components": {
-+            "engine":  ("sttgstvosk", "STTGstVosk"),
-+            "model":   ("sttvoskmodel", "STTVoskModel"),
-+            "manager": ("sttvoskmodelmanagers", "stt_vosk_online_model_manager"),
-+        },
-+    },
-+    "whisper": {
-+        "name": "Whisper",
-+        "dependencies": (),
-+        "components": {
-+            "engine":  ("sttgstwhisper", "STTGstWhisper"),
-+            "model":   ("sttwhispermodel", "STTWhisperModel"),
-+            "manager": ("sttwhispermodelmanagers", "stt_whisper_online_model_manager"),
-+        },
-+    },
-+    "onnxasr": {
-+        "name": "onnx-asr",
-+        "dependencies": (
-+            STTBackendDependency("onnx_asr", "onnx-asr", ("cpu", "hub")),
-+        ),
-+        "components": {
-+            "engine":  ("sttgstonnxasr", "STTGstOnnxAsr"),
-+            "model":   ("sttonnxasrmodel", "STTOnnxAsrModel"),
-+            "manager": ("sttonnxasrmodelmanagers", "stt_onnxasr_online_model_manager"),
-+        },
-+    },
-+    "moonshine": {
-+        "name": "Moonshine",
-+        "dependencies": (
-+            STTBackendDependency("moonshine_voice", "moonshine-voice"),
-+        ),
-+        "components": {
-+            "engine":  ("sttgstmoonshine", "STTGstMoonshine"),
-+            "model":   ("sttmoonshinemodel", "STTMoonshineModel"),
-+            "manager": ("sttmoonshinemodelmanagers", "stt_moonshine_online_model_manager"),
-+        },
-+    },
-+}
-+
-+STT_DEFAULT_BACKEND = "vosk"
-+
-+# Cache the lookups: they are queried from callbacks that can run on every
-+# model/locale change. Dropped by stt_invalidate_availability_cache().
-+_availability_cache = {}
-+
-+def stt_backends():
-+    return tuple(_BACKENDS.keys())
-+
-+def stt_backend_display_name(backend):
-+    entry = _BACKENDS.get(backend)
-+    if entry is None:
-+        return backend
-+    return entry["name"]
-+
-+def stt_backend_dependencies(backend):
-+    entry = _BACKENDS.get(backend)
-+    if entry is None:
-+        return ()
-+    return entry["dependencies"]
-+
-+def stt_invalidate_availability_cache():
-+    _availability_cache.clear()
-+    importlib.invalidate_caches()
-+
-+def stt_backend_missing_dependencies(backend, refresh=False):
-+    if refresh:
-+        stt_invalidate_availability_cache()
-+
-+    missing = []
-+    for dependency in stt_backend_dependencies(backend):
-+        available = _availability_cache.get(dependency.module)
-+        if available is None:
-+            available = dependency.available()
-+            _availability_cache[dependency.module] = available
-+            LOG_MSG.debug("module %s available: %s",
-+                          dependency.module, available)
-+        if not available:
-+            missing.append(dependency)
-+
-+    return missing
-+
-+def stt_backend_is_available(backend, refresh=False):
-+    return not stt_backend_missing_dependencies(backend, refresh=refresh)
-+
-+def stt_backend_install_command(backend):
-+    missing = stt_backend_missing_dependencies(backend)
-+    if not missing:
-+        return ""
-+
-+    return "pip install %s" % " ".join(
-+        dependency.pip_argument for dependency in missing)
-+
-+def stt_backend_component(backend, component):
-+    entry = _BACKENDS.get(backend)
-+    if entry is None:
-+        LOG_MSG.warning("unknown backend (%s)", backend)
-+        return None
-+
-+    location = entry["components"].get(component)
-+    if location is None:
-+        return None
-+
-+    module_name, attribute = location
-+    try:
-+        module = importlib.import_module(module_name)
-+    except Exception as error:
-+        LOG_MSG.warning("cannot import %s for backend %s (%s)",
-+                        module_name, backend, error)
-+        return None
-+
-+    value = getattr(module, attribute, None)
-+    if value is None:
-+        LOG_MSG.warning("%s has no attribute %s", module_name, attribute)
-+    return value
-+
-+def stt_backend_model_manager(backend):
-+    manager = stt_backend_component(backend, "manager")
-+    if manager is None:
-+        return None
-+
-+    try:
-+        return manager()
-+    except Exception as error:
-+        LOG_MSG.warning("cannot create model manager for %s (%s)",
-+                        backend, error)
-+        return None
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttconfigdialog.py IBus-Speech-To-Text-1.0.0/engine/sttconfigdialog.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttconfigdialog.py	2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttconfigdialog.py	2026-08-07 01:45:25.241228485 +0530
-@@ -27,27 +27,23 @@
- gi.require_version('Gtk', '4.0')
- gi.require_version('Adw', '1')
- 
--from gi.repository import Gtk, Gio, Adw
-+from gi.repository import Gtk, Gio, Gdk, GObject, Adw
- 
- from sttutils import *
- from sttshortcutrow import STTShortcutRow
- from sttshortcutdialog import STTShortcutDialog
- 
- from sttcurrentlocale import stt_current_locale
--from sttvoskmodelmanagers import stt_vosk_online_model_manager
--from sttwhispermodelmanagers import stt_whisper_online_model_manager
--from sttonnxasrmodelmanagers import stt_onnxasr_online_model_manager
--from sttmoonshinemodelmanagers import stt_moonshine_online_model_manager
--from sttvoskmodel import STTVoskModel
--from sttwhispermodel import STTWhisperModel
--from sttonnxasrmodel import STTOnnxAsrModel
--from sttmoonshinemodel import STTMoonshineModel
- from sttmodelchooserdialog import STTModelChooserDialog
- 
--from sttgstvosk import STTGstVosk
--from sttgstwhisper import STTGstWhisper
--from sttgstonnxasr import STTGstOnnxAsr
--from sttgstmoonshine import STTGstMoonshine
-+from sttbackenddeps import (STT_DEFAULT_BACKEND,
-+                            stt_backend_component,
-+                            stt_backend_display_name,
-+                            stt_backend_install_command,
-+                            stt_backend_is_available,
-+                            stt_backend_missing_dependencies,
-+                            stt_backend_model_manager,
-+                            stt_invalidate_availability_cache)
- 
- LOG_MSG=logging.getLogger()
- 
-@@ -64,6 +60,11 @@
-     onnxasr_check = Gtk.Template.Child()
-     moonshine_check = Gtk.Template.Child()
- 
-+    vosk_action_row      = Gtk.Template.Child()
-+    whisper_action_row   = Gtk.Template.Child()
-+    onnxasr_action_row   = Gtk.Template.Child()
-+    moonshine_action_row = Gtk.Template.Child()
-+
-     tab_stack    = Gtk.Template.Child()
-     tab_switcher = Gtk.Template.Child()
- 
-@@ -102,18 +103,20 @@
-         self._utterances_dict = {}
-         self._no_model_toast = None
-         self._unsupported_locale_toast = None
-+        self._missing_deps_toast = None
-         self._model = None
-         self._suppress_language_cb = False
-+        self._suppress_engine_cb = False
-         self._engine = None
- 
-         self._settings=Gio.Settings.new("org.freedesktop.ibus.engine.stt")
-         self._settings.bind("preload", self.preload_model_switch, "active", Gio.SettingsBindFlags.DEFAULT)
-         self._settings.bind("active-on-start", self.active_on_start_switch, "active", Gio.SettingsBindFlags.DEFAULT)
- 
--        stt_vosk_online_model_manager()
--        stt_whisper_online_model_manager()
--        stt_onnxasr_online_model_manager()
--        stt_moonshine_online_model_manager()
-+        self._backend_subtitles = {
-+            backend: (row.get_subtitle() or "")
-+            for backend, row in self._backend_action_rows().items()
-+        }
- 
-         # Load current locale
-         self._current_locale = stt_current_locale()
-@@ -121,25 +124,11 @@
-         self._override_file_changed_id=self._current_locale.connect("override-file-changed", self._override_file_changed_cb)
-         self._override_file_written=False
- 
--        backend = self._settings.get_string("backend")
--        self._suppress_engine_cb = True
--        if backend == "whisper":
--            self.whisper_check.set_active(True)
--        elif backend == "onnxasr":
--            self.onnxasr_check.set_active(True)
--        elif backend == "moonshine":
--            self.moonshine_check.set_active(True)
--        else:
--            self.vosk_check.set_active(True)
--        self._suppress_engine_cb = False
-+        self._select_backend_button(self._backend())
- 
-         self._locale_list = []
-         self._locale_names = Gtk.StringList()
--        self._populate_locale_list()
--        self._suppress_language_cb = True
--        self.language_dropdown.set_model(self._locale_names)
--        self._suppress_language_cb = False
--        self._select_current_locale_in_dropdown()
-+        self._refresh_locale_dropdown()
- 
-         if self._current_locale.default_locale:
-             self._suppress_language_cb = True
-@@ -156,17 +145,81 @@
- 
-         self._update_voice_commands_visibility()
- 
--        if self._model is None or not self._model.available():
--            self._engine_has_no_model()
--        elif self._valid_formatting_file == False:
--            self._unsupported_locale()
--
-         self._toast_action=Gio.SimpleAction.new("manage_model", None)
-+        self._install_action=Gio.SimpleAction.new("install_backend", None)
-         action_group=Gio.SimpleActionGroup.new()
-         action_group.insert(self._toast_action)
-+        action_group.insert(self._install_action)
-         self.insert_action_group("toast", action_group)
-         self._toast_action.connect("activate",
-                                    self._manage_model_action_activated)
-+        self._install_action.connect("activate",
-+                                     self._install_action_activated)
-+
-+        self._update_backend_rows()
-+        self._refresh_status_messages()
-+        self._backend_was_available = stt_backend_is_available(self._backend())
-+        self.connect("notify::is-active", self._window_active_cb)
-+
-+    def _window_active_cb(self, *_args):
-+        if not self.get_property("is-active"):
-+            return
-+
-+        stt_invalidate_availability_cache()
-+        available = stt_backend_is_available(self._backend())
-+        if available == self._backend_was_available:
-+            self._update_backend_rows()
-+            return
-+
-+        self._backend_was_available = available
-+        self._reload_backend()
-+
-+    def _backend(self):
-+        backend = self._settings.get_string("backend")
-+        if backend in ("", None):
-+            return STT_DEFAULT_BACKEND
-+        return backend
-+
-+    def _backend_check_buttons(self):
-+        return {
-+            "vosk":      self.vosk_check,
-+            "whisper":   self.whisper_check,
-+            "onnxasr":   self.onnxasr_check,
-+            "moonshine": self.moonshine_check,
-+        }
-+
-+    def _backend_action_rows(self):
-+        return {
-+            "vosk":      self.vosk_action_row,
-+            "whisper":   self.whisper_action_row,
-+            "onnxasr":   self.onnxasr_action_row,
-+            "moonshine": self.moonshine_action_row,
-+        }
-+
-+    def _backend_for_check_button(self, button):
-+        for backend, check in self._backend_check_buttons().items():
-+            if check == button:
-+                return backend
-+        return None
-+
-+    def _select_backend_button(self, backend):
-+        button = self._backend_check_buttons().get(backend)
-+        if button is None:
-+            button = self.vosk_check
-+
-+        self._suppress_engine_cb = True
-+        button.set_active(True)
-+        self._suppress_engine_cb = False
-+
-+    def _update_backend_rows(self):
-+        for backend, row in self._backend_action_rows().items():
-+            subtitle = self._backend_subtitles.get(backend, "")
-+            if stt_backend_is_available(backend):
-+                row.set_subtitle(subtitle)
-+            elif subtitle:
-+                row.set_subtitle(_("%s · Not installed") % subtitle)
-+            else:
-+                row.set_subtitle(_("Not installed"))
- 
- 
-     def _create_engine(self):
-@@ -180,15 +233,23 @@
-             self._engine.destroy()
-             self._engine = None
- 
--        backend = self._settings.get_string("backend")
--        if backend == "whisper":
--            self._engine = STTGstWhisper(current_locale=self._current_locale)
--        elif backend == "onnxasr":
--            self._engine = STTGstOnnxAsr(current_locale=self._current_locale)
--        elif backend == "moonshine":
--            self._engine = STTGstMoonshine(current_locale=self._current_locale)
--        else:
--            self._engine = STTGstVosk(current_locale=self._current_locale)
-+        backend = self._backend()
-+        if not stt_backend_is_available(backend):
-+            LOG_MSG.warning("backend %s is missing its dependencies, "
-+                            "no engine created", backend)
-+            return
-+
-+        engine_class = stt_backend_component(backend, "engine")
-+        if engine_class is None:
-+            return
-+
-+        try:
-+            self._engine = engine_class(current_locale=self._current_locale)
-+        except Exception as error:
-+            LOG_MSG.error("cannot create engine for backend %s (%s)",
-+                          backend, error)
-+            self._engine = None
-+            return
- 
-         self._engine.connect("model-changed", self._engine_model_changed_cb)
-         self._engine.preload()
-@@ -204,11 +265,13 @@
-                 and self._current_locale.locale not in self._locale_list):
-             self._append_locale_option(self._current_locale.locale)
- 
--        backend = self._settings.get_string("backend")
-+        backend = self._backend()
-         if backend == "moonshine":
--            supported = stt_moonshine_online_model_manager().supported_locales()
-+            manager = stt_backend_model_manager("moonshine")
-         else:
--            supported = stt_vosk_online_model_manager().supported_locales()
-+            manager = stt_backend_model_manager("vosk")
-+
-+        supported = manager.supported_locales() if manager is not None else []
- 
-         _EXCLUDED = {"multilingual"}
- 
-@@ -218,6 +281,16 @@
-             if loc not in self._locale_list:
-                 self._append_locale_option(loc)
- 
-+    def _refresh_locale_dropdown(self):
-+        # Rebuild the language list and hand it to the dropdown.
-+        self._populate_locale_list()
-+
-+        self._suppress_language_cb = True
-+        self.language_dropdown.set_model(self._locale_names)
-+        self._suppress_language_cb = False
-+
-+        self._select_current_locale_in_dropdown()
-+
-     def _append_locale_option(self, locale_str):
-         if locale_str in (None, "", "None", "multilingual"):
-             return
-@@ -253,23 +326,36 @@
-                 self._model.disconnect_by_func(self._model_changed_cb)
-             except TypeError:
-                 pass
-+            self._model = None
- 
--        backend = self._settings.get_string("backend")
-+        backend = self._backend()
-         locale_str = self._current_locale.locale
- 
--        if backend == "whisper":
--            self._model = STTWhisperModel(locale_str=locale_str)
--        elif backend == "onnxasr":
--            self._model = STTOnnxAsrModel(locale_str=locale_str)
--        elif backend == "moonshine":
--            self._model = STTMoonshineModel(locale_str=locale_str)
--        else:
--            self._model = STTVoskModel(locale_str=locale_str)
-+        if stt_backend_is_available(backend):
-+            model_class = stt_backend_component(backend, "model")
-+            if model_class is not None:
-+                try:
-+                    self._model = model_class(locale_str=locale_str)
-+                except Exception as error:
-+                    LOG_MSG.error("cannot create model for backend %s (%s)",
-+                                  backend, error)
-+
-+        if self._model is not None:
-+            self._model.connect("changed", self._model_changed_cb)
- 
--        self._model.connect("changed", self._model_changed_cb)
-         self._update_model_info()
- 
-     def _update_model_info(self):
-+        backend = self._backend()
-+
-+        if stt_backend_missing_dependencies(backend):
-+            self.model_info_row.set_title(
-+                _("%s is not installed") % stt_backend_display_name(backend))
-+            self.model_info_row.set_subtitle(
-+                stt_backend_install_command(backend))
-+            self.change_model_button.set_label(_("Install…"))
-+            return
-+
-         if self._model is None or not self._model.available():
-             self.model_info_row.set_title(_("No model downloaded"))
-             self.model_info_row.set_subtitle(
-@@ -288,16 +374,8 @@
-                 else _("Installed manually"))
-             return
- 
--        backend = self._settings.get_string("backend")
--        if backend == "whisper":
--            manager = stt_whisper_online_model_manager()
--        elif backend == "onnxasr":
--            manager = stt_onnxasr_online_model_manager()
--        elif backend == "moonshine":
--            manager = stt_moonshine_online_model_manager()
--        else:
--            manager = stt_vosk_online_model_manager()
--        desc = manager.get_model_description(model_name)
-+        manager = stt_backend_model_manager(backend)
-+        desc = manager.get_model_description(model_name) if manager else None
- 
-         self.model_info_row.set_title(model_name)
- 
-@@ -347,6 +425,9 @@
-                     self.model_info_row.set_subtitle(size)
- 
-     def _auto_prompt_model_download(self):
-+        if not stt_backend_is_available(self._backend()):
-+            return
-+
-         if self._model is not None and not self._model.available():
-             dialog = STTModelChooserDialog(model=self._model)
-             dialog.set_transient_for(self)
-@@ -355,9 +436,134 @@
-     def _model_changed_cb(self, model):
-         self._update_model_info()
- 
-+    def _copy_to_clipboard(self, text):
-+        clipboard = self.get_clipboard()
-+        if clipboard is None:
-+            return
-+
-+        try:
-+            clipboard.set_content(
-+                Gdk.ContentProvider.new_for_value(GObject.Value(str, text)))
-+        except Exception as error:
-+            LOG_MSG.warning("cannot copy to clipboard (%s)", error)
-+
-+    def _install_dialog_body(self, backend, missing):
-+        modules = "\n".join("    • %s" % dep.requirement for dep in missing)
-+        return _("The %(backend)s backend needs python modules that are not "
-+                 "installed on this system:\n\n"
-+                 "%(modules)s\n\n"
-+                 "Install them in a terminal with the following command, "
-+                 "then press “Check Again”:\n\n"
-+                 "    %(command)s") % {
-+                     "backend": stt_backend_display_name(backend),
-+                     "modules": modules,
-+                     "command": stt_backend_install_command(backend)}
-+
-+    def _present_install_dialog(self, backend):
-+        missing = stt_backend_missing_dependencies(backend, refresh=True)
-+        if not missing:
-+            return False
-+
-+        heading = _("%s Is Not Installed") % stt_backend_display_name(backend)
-+        body = self._install_dialog_body(backend, missing)
-+        command = stt_backend_install_command(backend)
-+
-+        # Adw.AlertDialog needs libadwaita 1.5, fall back on Adw.MessageDialog.
-+        if hasattr(Adw, "AlertDialog"):
-+            dialog = Adw.AlertDialog(heading=heading, body=body)
-+            self._setup_install_dialog(dialog, backend, command)
-+            dialog.present(self)
-+        else:
-+            dialog = Adw.MessageDialog(transient_for=self, modal=True,
-+                                       heading=heading, body=body)
-+            self._setup_install_dialog(dialog, backend, command)
-+            dialog.present()
-+
-+        return True
-+
-+    def _setup_install_dialog(self, dialog, backend, command):
-+        dialog.add_response("close", _("Close"))
-+        dialog.add_response("copy", _("Copy Command"))
-+        dialog.add_response("recheck", _("Check Again"))
-+        dialog.set_response_appearance("recheck",
-+                                       Adw.ResponseAppearance.SUGGESTED)
-+        dialog.set_default_response("recheck")
-+        dialog.set_close_response("close")
-+        dialog.connect("response", self._install_dialog_response_cb,
-+                       backend, command)
-+
-+    def _install_dialog_response_cb(self, dialog, response, backend, command):
-+        if response == "copy":
-+            self._copy_to_clipboard(command)
-+            self.toast_overlay.add_toast(
-+                Adw.Toast(title=_("Command copied to the clipboard"),
-+                          timeout=3))
-+            return
-+
-+        if response != "recheck":
-+            return
-+
-+        if not stt_backend_is_available(backend, refresh=True):
-+            self.toast_overlay.add_toast(
-+                Adw.Toast(title=_("%s is still not installed")
-+                          % stt_backend_display_name(backend), timeout=5))
-+            return
-+
-+        self._update_backend_rows()
-+        if backend != self._backend():
-+            self._apply_backend(backend)
-+        else:
-+            self._reload_backend()
-+
-+    def _install_action_activated(self, _action, _param):
-+        self._present_install_dialog(self._backend())
-+
-+    def _backend_not_installed(self):
-+        if self._missing_deps_toast != None:
-+            return
-+
-+        if self._no_model_toast != None:
-+            self._no_model_toast.dismiss()
-+            self._no_model_toast = None
-+        if self._unsupported_locale_toast != None:
-+            self._unsupported_locale_toast.dismiss()
-+            self._unsupported_locale_toast = None
-+
-+        self._missing_deps_toast = Adw.Toast(
-+            title=_("%s is not installed")
-+                  % stt_backend_display_name(self._backend()),
-+            timeout=0,
-+            button_label=_("How to Install"),
-+            action_name="toast.install_backend")
-+        self._missing_deps_toast.connect("dismissed", self._toast_dismissed)
-+        self.toast_overlay.add_toast(self._missing_deps_toast)
-+
-+    def _refresh_status_messages(self):
-+        if not stt_backend_is_available(self._backend()):
-+            self._backend_not_installed()
-+            return
-+
-+        if self._missing_deps_toast != None:
-+            self._missing_deps_toast.dismiss()
-+            self._missing_deps_toast = None
-+
-+        if (self._model is None or not self._model.available()
-+                or self._engine is None or not self._engine.has_model()):
-+            self._engine_has_no_model()
-+            return
-+
-+        if self._no_model_toast != None:
-+            self._no_model_toast.dismiss()
-+            self._no_model_toast = None
-+
-+        if self._valid_formatting_file == False:
-+            self._unsupported_locale()
-+        elif self._unsupported_locale_toast != None:
-+            self._unsupported_locale_toast.dismiss()
-+            self._unsupported_locale_toast = None
- 
-     def _update_voice_commands_visibility(self):
--        is_vosk = (self._settings.get_string("backend") == "vosk")
-+        is_vosk = (self._backend() == "vosk")
-         self.vc_whisper_warning.set_visible(not is_vosk)
-         self.voice_commands_group.set_visible(is_vosk)
- 
-@@ -372,40 +578,40 @@
-         if getattr(self, '_suppress_engine_cb', False):
-             return
- 
--        if button == self.vosk_check:
--            backend = "vosk"
--        elif button == self.moonshine_check:
--            backend = "moonshine"
--        elif button == self.whisper_check:
--            backend = "whisper"
--        else:
--            backend = "onnxasr"
-+        backend = self._backend_for_check_button(button)
-+        if backend is None:
-+            return
- 
--        current = self._settings.get_string("backend")
-+        current = self._backend()
-         if backend == current:
-             return
- 
--        self._settings.set_string("backend", backend)
--        old_locale = self._current_locale.locale
--        self._populate_locale_list()
--        self._suppress_language_cb = True
--        self.language_dropdown.set_model(self._locale_names)
--        self._suppress_language_cb = False
-+        if not stt_backend_is_available(backend, refresh=True):
-+            self._select_backend_button(current)
-+            self._update_backend_rows()
-+            self._present_install_dialog(backend)
-+            return
- 
--        if old_locale in self._locale_list:
--            self._suppress_language_cb = True
--            self.language_dropdown.set_selected(
--                self._locale_list.index(old_locale))
--            self._suppress_language_cb = False
-+        self._apply_backend(backend)
- 
--        self._init_model()
-+    def _apply_backend(self, backend):
-+        self._settings.set_string("backend", backend)
-+        self._select_backend_button(backend)
-+        self._reload_backend(prompt_download=True)
- 
-+    def _reload_backend(self, prompt_download=False):
-+        self._refresh_locale_dropdown()
-+        self._init_model()
-         self._create_engine()
-         self._update_voice_commands_visibility()
-         self._empty_shortcut_page()
-         self._load_utterances()
-+        self._update_backend_rows()
-+        self._refresh_status_messages()
- 
--        if self._model is not None and not self._model.available():
-+        if (prompt_download
-+                and self._model is not None
-+                and not self._model.available()):
-             self._auto_prompt_model_download()
- 
-     @Gtk.Template.Callback()
-@@ -441,6 +647,14 @@
- 
-     @Gtk.Template.Callback()
-     def change_model_clicked_cb(self, *_args):
-+        backend = self._backend()
-+        if not stt_backend_is_available(backend, refresh=True):
-+            self._present_install_dialog(backend)
-+            return
-+
-+        if self._model is None:
-+            self._reload_backend()
-+
-         if self._model != None:
-             dialog = STTModelChooserDialog(model=self._model)
-             dialog.set_transient_for(self)
-@@ -458,11 +672,7 @@
-         self._select_current_locale_in_dropdown()
- 
-         if self._current_locale.locale not in self._locale_list:
--            self._populate_locale_list()
--            self._suppress_language_cb = True
--            self.language_dropdown.set_model(self._locale_names)
--            self._suppress_language_cb = False
--            self._select_current_locale_in_dropdown()
-+            self._refresh_locale_dropdown()
- 
-         self._init_model()
-         self._load_current_locale()
-@@ -541,20 +751,7 @@
-     def _load_current_locale(self):
-         self._empty_shortcut_page()
-         self._load_utterances()
--
--        if not self._engine.has_model():
--            self._engine_has_no_model()
--            return
--
--        if self._no_model_toast != None:
--            self._no_model_toast.dismiss()
--            self._no_model_toast = None
--
--        if not self._valid_formatting_file:
--            self._unsupported_locale()
--        elif self._unsupported_locale_toast != None:
--            self._unsupported_locale_toast.dismiss()
--            self._unsupported_locale_toast = None
-+        self._refresh_status_messages()
- 
-     def _apply_change(self):
-         LOG_MSG.debug("override file being written")
-@@ -608,6 +805,10 @@
-         self._present_shortcut_dialog(row)
- 
-     def _present_shortcut_dialog(self, row):
-+        if self._engine is None:
-+            LOG_MSG.warning("no engine available, cannot edit shortcuts")
-+            return
-+
-         dialog = STTShortcutDialog(
-             row=row, engine=self._engine, transient_for=self)
-         dialog.connect("response", self._shortcut_dialog_response_cb)
-@@ -781,7 +982,9 @@
-         self._auto_prompt_model_download()
- 
-     def _toast_dismissed(self, toast):
--        if toast == self._no_model_toast:
-+        if toast == self._missing_deps_toast:
-+            self._missing_deps_toast=None
-+        elif toast == self._no_model_toast:
-             self._no_model_toast=None
- 
-             # Display the other message if needed
-@@ -792,11 +995,14 @@
- 
-     def _unsupported_locale(self):
-         # Careful: we can have no formatting file but an overriding one !!
-+        if self._missing_deps_toast != None:
-+            return
-+
-         if self._no_model_toast != None:
-             return
- 
-         # Formatting files only exist for vosk; other engines have no files to find
--        if self._settings.get_string("backend") != "vosk":
-+        if self._backend() != "vosk":
-             if self._unsupported_locale_toast is not None:
-                 self._unsupported_locale_toast.dismiss()
-                 self._unsupported_locale_toast = None
-@@ -816,6 +1022,9 @@
-         self.toast_overlay.add_toast(self._unsupported_locale_toast)
- 
-     def _engine_has_no_model(self):
-+        if self._missing_deps_toast != None:
-+            return
-+
-         if self._no_model_toast != None:
-             return
- 
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttgstfactory.py IBus-Speech-To-Text-1.0.0/engine/sttgstfactory.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttgstfactory.py	2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttgstfactory.py	2026-08-07 01:46:01.026792604 +0530
-@@ -29,6 +29,10 @@
- from sttgstonnxasr import STTGstOnnxAsr
- from sttgstmoonshine import STTGstMoonshine
- 
-+from sttbackenddeps import (stt_backend_display_name,
-+                            stt_backend_install_command,
-+                            stt_backend_is_available)
-+
- LOG_MSG=logging.getLogger()
- 
- class STTGstFactory(GObject.GObject):
-@@ -45,11 +49,21 @@
-         self.__update_preloaded_engine()
- 
-     def new_engine(self):
--        engine=None if self._current_engine is None else self._current_engine()
-+        engine = None if self._current_engine is None else self._current_engine()
-+        if engine is not None and engine.pipeline is None:
-+            LOG_MSG.debug("cached engine was already destroyed, creating a new one")
-+            engine = None
-         if engine is None:
-             LOG_MSG.debug("new engine")
-             # Check backend setting
-             backend = self.__settings.get_string("backend")
-+            if not stt_backend_is_available(backend):
-+                LOG_MSG.error("%s backend is selected but not installed. "
-+                              "Install it with: %s",
-+                              stt_backend_display_name(backend),
-+                              stt_backend_install_command(backend))
-+                LOG_MSG.error("falling back to the Vosk backend")
-+                backend = "vosk"
-             if backend == "whisper":
-                 LOG_MSG.info("Using Whisper backend")
-                 engine=STTGstWhisper()
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttgstmoonshine.py IBus-Speech-To-Text-1.0.0/engine/sttgstmoonshine.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttgstmoonshine.py	2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttgstmoonshine.py	2026-08-07 01:38:55.356082244 +0530
-@@ -13,38 +13,39 @@
- 
- SAMPLE_RATE = 16000
- 
--try:
--    from moonshine_voice import (
--        Transcriber,
--        TranscriptEventListener,
--        ModelArch,
--    )
--    MOONSHINE_AVAILABLE = True
--except ImportError:
--    LOG_MSG.warning("moonshine_voice not available. Install with: pip install moonshine-voice")
--    MOONSHINE_AVAILABLE = False
--    TranscriptEventListener = object
-+_LISTENER_CLASS = None
- 
--class _LineListener(TranscriptEventListener):
-+def _line_listener_class():
-+    #Build the listener subclass on first use
-+    global _LISTENER_CLASS
-+    if _LISTENER_CLASS is not None:
-+        return _LISTENER_CLASS
- 
--    def __init__(self, engine):
--        if MOONSHINE_AVAILABLE:
-+    from moonshine_voice import TranscriptEventListener
-+
-+    class _LineListener(TranscriptEventListener):
-+
-+        def __init__(self, engine):
-             super().__init__()
--        self._engine = engine
--        self._emitted_lines = []
-+            self._engine = engine
-+            self._emitted_lines = []
- 
--    def reset(self):
--        self._emitted_lines.clear()
-+        def reset(self):
-+            self._emitted_lines.clear()
-+
-+        def on_line_completed(self, event):
-+            line = event.line
-+            if any(line is seen for seen in self._emitted_lines):
-+                return
-+            self._emitted_lines.append(line)
-+            text = (line.text or "").strip()
-+            if text:
-+                LOG_MSG.info("Moonshine transcription result: '%s'", text)
-+                GLib.idle_add(self._engine._emit_text, text)
-+
-+    _LISTENER_CLASS = _LineListener
-+    return _LISTENER_CLASS
- 
--    def on_line_completed(self, event):
--        line = event.line
--        if any(line is seen for seen in self._emitted_lines):
--            return
--        self._emitted_lines.append(line)
--        text = (line.text or "").strip()
--        if text:
--            LOG_MSG.info("Moonshine transcription result: '%s'", text)
--            GLib.idle_add(self._engine._emit_text, text)
- 
- class STTGstMoonshine(STTGstBase):
-     __gtype_name__ = 'STTGstMoonshine'
-@@ -93,6 +94,7 @@
-         self._tx_lock = threading.Lock()
-         self._session_active = False
-         self._stopping = False
-+        self._destroyed = False
- 
-         self._process_queue = queue.Queue()
-         self._stop_processing = False
-@@ -103,11 +105,11 @@
- 
-     def __del__(self):
-         try:
--            if LOG_MSG is not None:
--                LOG_MSG.info("Moonshine __del__")
-             self._stop_processing = True
--            if self._process_thread is not None:
--                self._process_thread.join(timeout=2.0)
-+            thread = self._process_thread
-+            self._process_thread = None
-+            if thread is not None:
-+                thread.join(timeout=2.0)
-         except Exception:
-             pass
-         try:
-@@ -116,24 +118,36 @@
-             pass
- 
-     def destroy(self):
-+        if self._destroyed:
-+            LOG_MSG.debug("Moonshine engine already destroyed")
-+            return
-+        self._destroyed = True
-+
-         self._stop_processing = True
--        if self._process_thread is not None:
--            self._process_thread.join(timeout=2.0)
- 
--        self._current_locale.disconnect(self._locale_id)
--        self._locale_id = 0
-+        thread = self._process_thread
-+        self._process_thread = None
-+        if thread is not None:
-+            thread.join(timeout=2.0)
-+            if thread.is_alive():
-+                LOG_MSG.warning("Moonshine worker thread did not stop in time")
-+
-+        if self._locale_id != 0:
-+            self._current_locale.disconnect(self._locale_id)
-+            self._locale_id = 0
- 
--        if self._model_id != 0:
-+        if self._model_id != 0 and self._model is not None:
-             self._model.disconnect(self._model_id)
-             self._model_id = 0
- 
-         with self._tx_lock:
-             self._teardown_transcriber()
- 
--        if self._appsink is not None and getattr(self, "_new_sample_id", 0) !=0:
--            self._appsink.disconnect(self._new_sample_id)
--            self._new_sample_id = 0
-+        if self._appsink is not None and self._on_new_sample_id != 0:
-+            self._appsink.disconnect(self._on_new_sample_id)
-+            self._on_new_sample_id = 0
-         self._appsink = None
-+
-         LOG_MSG.info("Moonshine.destroy() called")
-         super().destroy()
- 
-@@ -150,14 +164,18 @@
-         self._listener = None
- 
-     def _load_moonshine_model(self, model_path, model_arch):
--        if not MOONSHINE_AVAILABLE:
--            LOG_MSG.error("moonshine_voice not available")
-+        try:
-+            from moonshine_voice import ModelArch, Transcriber
-+        except Exception as e:
-+            LOG_MSG.error("moonshine_voice not available (%s). "
-+                          "Install with: pip install moonshine-voice", e)
-             return False
-+
-         try:
--            arch = model_arch if model_arch is not None else ModelArch.BASE
-+            arch = (ModelArch(model_arch) if model_arch is not None else ModelArch.BASE)
-             LOG_MSG.info("Loading Moonshine model: %s (arch=%s)", model_path, arch)
-             transcriber = Transcriber(model_path=model_path, model_arch=arch)
--            listener = _LineListener(self)
-+            listener = _line_listener_class()(self)
-             transcriber.add_listener(listener)
-             with self._tx_lock:
-                 self._teardown_transcriber()
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttmoonshinemodelmanagers.py IBus-Speech-To-Text-1.0.0/engine/sttmoonshinemodelmanagers.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttmoonshinemodelmanagers.py	2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttmoonshinemodelmanagers.py	2026-08-07 01:46:55.086648952 +0530
-@@ -1,5 +1,9 @@
- import os
-+import sys
-+import importlib.machinery
- import logging
-+import importlib
-+import importlib.util
- import threading
- 
- from pathlib import Path
-@@ -9,23 +13,14 @@
- 
- LOG_MSG = logging.getLogger()
- 
--
--try:
--    from moonshine_voice import ModelArch
--    from moonshine_voice.download import (
--        MODEL_INFO,
--        find_model_info,
--        get_components_for_model_info,
--        get_model_for_language,
--    )
--    from moonshine_voice.download_file import get_cache_dir
--    MOONSHINE_AVAILABLE = True
--except Exception as e:
--    LOG_MSG.warning("moonshine_voice model catalog unavailable (%s). "
--                    "Install/upgrade with: pip install -U moonshine-voice", e)
--    MOONSHINE_AVAILABLE = False
--    MODEL_INFO = {}
--    ModelArch = None
-+_ARCH_NAMES = {
-+    0: "tiny",
-+    1: "base",
-+    2: "tiny-streaming",
-+    3: "base-streaming",
-+    4: "small-streaming",
-+    5: "medium-streaming",
-+}
- 
- _ARCH_SIZES = {
-     "tiny":             "~50 MB",
-@@ -46,15 +41,9 @@
- }
- 
- def _arch_to_string(model_arch):
--    return {
--        0: "tiny",
--        1: "base",
--        2: "tiny-streaming",
--        3: "base-streaming",
--        4: "small-streaming",
--        5: "medium-streaming",
--    }.get(int(model_arch), "base")
--
-+    if model_arch is None:
-+        return "base"
-+    return _ARCH_NAMES.get(int(model_arch), "base")
- 
- class STTDownloadState(float, Enum):
-     STOPPED = -1.0
-@@ -67,26 +56,116 @@
-         return None
-     return locale_str[0:2].lower()
- 
--def _all_model_infos():
--    for lang, entry in MODEL_INFO.items():
--        for model in entry.get("models", []):
--            yield model["model_name"], lang, model
--
--def _expected_model_path(model_info):
--    cache_dir = get_cache_dir()
--    folder = model_info["download_url"].replace("https://", "")
--    return Path(cache_dir, folder)
--
--def _model_present(model_info):
--    root = _expected_model_path(model_info)
--    if not root.is_dir():
-+def moonshine_installed():
-+    try:
-+        if importlib.machinery.PathFinder.find_spec("moonshine_voice", sys.path):
-+            return True
-+        importlib.invalidate_caches()
-+        return bool(importlib.machinery.PathFinder.find_spec("moonshine_voice",
-+                                                             sys.path))
-+    except (ImportError, ValueError, TypeError, AttributeError) as error:
-+        LOG_MSG.debug("cannot look up moonshine_voice (%s)", error)
-         return False
-+
-+def _moonshine_cache_dir():
-+    #Same directory as moonshine_voice.download_file.get_cache_dir(), computed without importing the package
-+    override = os.environ.get("MOONSHINE_VOICE_CACHE")
-+    if override:
-+        return Path(override)
-+
-+    xdg = os.environ.get("XDG_CACHE_HOME")
-+    if xdg:
-+        return Path(xdg) / "moonshine_voice"
-+
-+    return Path.home() / ".cache" / "moonshine_voice"
-+
-+_CATALOG = None          # {model_name: {...}}
-+_CATALOG_LOCALES = None  # {lang: [model_name, ...]}
-+
-+def _build_catalog():
-+    from moonshine_voice.download import find_model_info, supported_languages
-+
-+    models = {}
-+    locales = {}
-+
-+    for lang in supported_languages():
-+        for arch_value in sorted(_ARCH_NAMES):
-+            try:
-+                info = find_model_info(lang, arch_value)
-+            except Exception:
-+                # This language simply has no model for that architecture.
-+                continue
-+
-+            arch_name = _ARCH_NAMES[arch_value]
-+            name = "%s-%s" % (arch_name, lang)
-+            models[name] = {
-+                "name": name,
-+                "lang": lang,
-+                "arch": arch_value,
-+                "arch_name": arch_name,
-+                "download_url": info.get("download_url", ""),
-+            }
-+            locales.setdefault(lang, []).append(name)
-+
-+    return models, locales
-+
-+def _catalog():
-+    global _CATALOG, _CATALOG_LOCALES
-+
-+    if not moonshine_installed():
-+        _CATALOG = None
-+        _CATALOG_LOCALES = None
-+        return {}, {}
-+
-+    if _CATALOG is not None:
-+        return _CATALOG, _CATALOG_LOCALES
-+
-+    try:
-+        models, locales = _build_catalog()
-+    except Exception as error:
-+        LOG_MSG.warning("moonshine_voice model catalog unavailable (%s). "
-+                        "Install/upgrade with: pip install -U moonshine-voice",
-+                        error)
-+        return {}, {}
-+
-+    if not models:
-+        LOG_MSG.warning("moonshine_voice returned an empty model catalog")
-+        return {}, {}
-+
-+    _CATALOG = models
-+    _CATALOG_LOCALES = locales
-+    LOG_MSG.debug("moonshine catalog built (%d models, %d languages)",
-+                  len(models), len(locales))
-+    return _CATALOG, _CATALOG_LOCALES
-+
-+def _model_root(entry):
-+    url = entry.get("download_url") or ""
-+    if not url:
-+        return None
-+    return Path(_moonshine_cache_dir(), url.replace("https://", ""))
-+
-+def _model_present(entry):
-+    root = _model_root(entry)
-+    if root is None or not root.is_dir():
-+        return False
-+
-+    components = None
-     try:
--        components = get_components_for_model_info(model_info)
--    except Exception:
--        components = ["tokenizer.bin"]
--    return all((root / component).is_file() for component in components)
-+        from moonshine_voice.download import (find_model_info,
-+                                              get_components_for_model_info)
-+        components = get_components_for_model_info(
-+            find_model_info(entry["lang"], entry["arch"]))
-+    except Exception as error:
-+        LOG_MSG.debug("cannot list components of %s (%s)",
-+                      entry["name"], error)
- 
-+    if components:
-+        return all((root / component).is_file() for component in components)
-+
-+    try:
-+        return any(root.iterdir())
-+    except OSError:
-+        return False
- 
- class STTMoonshineModelDescription(GObject.Object):
-     __gtype_name__ = "STTMoonshineModelDescription"
-@@ -106,21 +185,32 @@
-         self.quality = init_model.quality if init_model is not None else ""
- 
-         self._operation = None
-+        self._downloaded_path = None
-         self.download_progress = STTDownloadState.STOPPED
- 
-     def _download_finished(self):
-         self._operation = None
-         self.download_progress = STTDownloadState.STOPPED
--        info = find_model_info(self.lang, self.arch)
--        if _model_present(info):
--            path = str(_expected_model_path(info))
-+
-+        path = self._downloaded_path
-+        self._downloaded_path = None
-+
-+        if path is None:
-+            entry = _catalog()[0].get(self.name)
-+            if entry is not None and _model_present(entry):
-+                path = str(_model_root(entry))
-+
-+        if path is not None:
-             self.paths = [path]
--            stt_moonshine_local_model_manager()._notify_added(self.name, path, self.lang)
-+            stt_moonshine_local_model_manager()._notify_added(
-+                self.name, path, self.lang)
-         return False
- 
-     def _download_thread(self, cancelled):
-         try:
--            get_model_for_language(self.lang, self.arch)
-+            from moonshine_voice import get_model_for_language
-+            path, _arch = get_model_for_language(self.lang, self.arch)
-+            self._downloaded_path = str(path)
-         except Exception as e:
-             LOG_MSG.error("Moonshine download failed (%s): %s", self.name, e)
-         if not cancelled.is_set():
-@@ -131,7 +221,7 @@
-     def start_downloading(self):
-         if self._operation is not None:
-             return
--        if not MOONSHINE_AVAILABLE:
-+        if not moonshine_installed():
-             LOG_MSG.error("cannot download, moonshine_voice not installed")
-             return
- 
-@@ -186,16 +276,18 @@
-         super().__init__()
-         self._present = {}
-         self._custom_paths = {}
--        self._scan_present_models()
-+        self._scanned_models = 0
- 
-     def _scan_present_models(self):
--        if not MOONSHINE_AVAILABLE:
-+        catalog, _locales = _catalog()
-+        if not catalog or self._scanned_models == len(catalog):
-             return
--        for model_name, lang, info in _all_model_infos():
--            info = dict(info, language=lang)
--            if _model_present(info):
--                self._present[model_name] = str(_expected_model_path(info))
--                LOG_MSG.debug("moonshine model present on disk (%s)", model_name)
-+
-+        self._scanned_models = len(catalog)
-+        for name, entry in catalog.items():
-+            if _model_present(entry):
-+                self._present[name] = str(_model_root(entry))
-+                LOG_MSG.debug("moonshine model present on disk (%s)", name)
- 
-     def _notify_added(self, model_name, path, lang=None):
-         self._present[model_name] = path
-@@ -206,44 +298,38 @@
-         self.emit("removed", model_name, path)
- 
-     def path_available(self, model_path):
--        return model_path in self._present.values() or model_path in self._custom_paths
-+        self._scan_present_models()
-+        return (model_path in self._present.values()
-+                or model_path in self._custom_paths)
- 
-     def get_best_path_for_model(self, model_name):
-         if model_name is None:
-             return None
-+        self._scan_present_models()
-         return self._present.get(model_name, None)
- 
-     def get_arch_for_model(self, model_name):
--        if not MOONSHINE_AVAILABLE:
--            return None
--        for name, lang, info in _all_model_infos():
--            if name == model_name:
--                return info["model_arch"]
--        return None
-+        entry = _catalog()[0].get(model_name)
-+        return entry["arch"] if entry is not None else None
- 
-     def get_lang_for_model(self, model_name):
--        if not MOONSHINE_AVAILABLE:
--            return None
--        for name, lang, info in _all_model_infos():
--            if name == model_name:
--                return lang
--        return None
-+        entry = _catalog()[0].get(model_name)
-+        return entry["lang"] if entry is not None else None
- 
-     @staticmethod
-     def _infer_arch_for_folder(model_path):
-         root = Path(model_path)
-+        name = root.name.lower()
-+
-         if (root / "streaming_config.json").is_file():
--            name = root.name.lower()
--            for token, arch in (("medium", ModelArch.MEDIUM_STREAMING),
--                                ("small", ModelArch.SMALL_STREAMING),
--                                ("base", ModelArch.BASE_STREAMING),
--                                ("tiny", ModelArch.TINY_STREAMING)):
-+            for token, arch in (("medium", 5), ("small", 4),
-+                                ("base", 3), ("tiny", 2)):
-                 if token in name:
-                     return arch
--            return ModelArch.SMALL_STREAMING
--        if "tiny" in root.name.lower():
--            return ModelArch.TINY
--        return ModelArch.BASE
-+            return 4
-+        if "tiny" in name:
-+            return 0
-+        return 1
- 
-     def register_custom_model_path(self, model_path, locale_str):
-         self._custom_paths[model_path] = locale_str
-@@ -252,7 +338,8 @@
-         self._custom_paths.pop(model_path, None)
- 
-     def custom_path_available(self, model_path):
--        return Path(model_path).is_dir() and (Path(model_path) / "tokenizer.bin").is_file()
-+        root = Path(model_path)
-+        return root.is_dir() and (root / "tokenizer.bin").is_file()
- 
- 
- _GLOBAL_LOCAL_MANAGER = None
-@@ -276,35 +363,43 @@
-         super().__init__()
-         self._models = {}
-         self._locales_dict = {}
--        self._build_catalog()
- 
-         local = stt_moonshine_local_model_manager()
-         local.connect("added", self._model_path_added_cb)
-         local.connect("removed", self._model_path_removed_cb)
- 
--    def _build_catalog(self):
--        if not MOONSHINE_AVAILABLE:
-+    def _ensure_catalog(self):
-+        catalog, locales = _catalog()
-+        if not catalog:
-+            self._models = {}
-+            self._locales_dict = {}
-+            return
-+        if len(self._models) == len(catalog):
-             return
--        for model_name, lang, info in _all_model_infos():
--            arch = info["model_arch"]
--            arch_str = _arch_to_string(arch)
-+
-+        local = stt_moonshine_local_model_manager()
-+        self._models = {}
-+        self._locales_dict = {}
-+
-+        for name, entry in catalog.items():
-+            arch_name = entry["arch_name"]
- 
-             desc = STTMoonshineModelDescription()
--            desc.name = model_name
--            desc.lang = lang
--            desc.locale = lang
--            desc.arch = arch
--            desc.type = arch_str
--            desc.url = info["download_url"]
--            desc.size = _ARCH_SIZES.get(arch_str, "")
--            desc.quality = _ARCH_QUALITY.get(arch_str, "")
-+            desc.name = name
-+            desc.lang = entry["lang"]
-+            desc.locale = entry["lang"]
-+            desc.arch = entry["arch"]
-+            desc.type = arch_name
-+            desc.url = entry["download_url"]
-+            desc.size = _ARCH_SIZES.get(arch_name, "")
-+            desc.quality = _ARCH_QUALITY.get(arch_name, "")
- 
--            path = stt_moonshine_local_model_manager().get_best_path_for_model(model_name)
-+            path = local.get_best_path_for_model(name)
-             if path is not None:
-                 desc.paths = [path]
- 
--            self._models[model_name] = desc
--            self._locales_dict.setdefault(lang, []).append(desc)
-+            self._models[name] = desc
-+            self._locales_dict.setdefault(entry["lang"], []).append(desc)
- 
-     def _model_path_added_cb(self, manager, model_name, model_path):
-         desc = self._models.get(model_name, None)
-@@ -322,15 +417,16 @@
-         self.emit("changed", desc)
- 
-     def get_model_description(self, model_name):
-+        self._ensure_catalog()
-         return self._models.get(model_name, None)
- 
-     def get_models_for_locale(self, locale_str):
-+        self._ensure_catalog()
-         lang = _lang_of_locale(locale_str)
-         return list(self._locales_dict.get(lang, []))
- 
-     def supported_locales(self):
--        if not MOONSHINE_AVAILABLE:
--            return []
-+        self._ensure_catalog()
-         return list(self._locales_dict.keys())
- 
- _GLOBAL_ONLINE_MANAGER = None
-diff -urN IBus-Speech-To-Text-1.0.0.orig/engine/sttmoonshinemodel.py IBus-Speech-To-Text-1.0.0/engine/sttmoonshinemodel.py
---- IBus-Speech-To-Text-1.0.0.orig/engine/sttmoonshinemodel.py	2026-07-14 03:00:00.000000000 +0530
-+++ IBus-Speech-To-Text-1.0.0/engine/sttmoonshinemodel.py	2026-08-07 01:39:38.932769188 +0530
-@@ -4,10 +4,7 @@
- from pathlib import Path
- from gi.repository import GObject, Gio
- 
--from sttmoonshinemodelmanagers import (
--    stt_moonshine_local_model_manager,
--    MOONSHINE_AVAILABLE,
--)
-+from sttmoonshinemodelmanagers import stt_moonshine_local_model_manager
- 
- LOG_MSG = logging.getLogger()
- 
-@@ -79,7 +76,7 @@
-                 return
-             self._model_name = None
-             self._model_path = model
--            self._model_arch = local._infer_arch_for_folder(model) if MOONSHINE_AVAILABLE else None
-+            self._model_arch = local._infer_arch_for_folder(model)
-             local.register_custom_model_path(model, self._locale_str)
-             self._valid_model = local.custom_path_available(model)
-         else:

diff --git a/sources b/sources
index 3d7d1cf..8df341d 100644
--- a/sources
+++ b/sources
@@ -1 +1 @@
-SHA512 (1.0.0.tar.gz) = 7b5d6cbea06cfcdb03420cf7160738ef435726af5192b3d4435a58afe902edb1b8bbc50b5b0b594fc640749f60622a6cf81d79b9fec6c6b2ceb6977551f884c8
+SHA512 (1.1.0.tar.gz) = 8c9fbdbd22443cfb94bc692ee218ef7b982f3469cb0f546b4a1f135a088b92a5c83911a1b6ef808c8bdb2716782eda706f45822c01f6aadc819f9fe2ce49ede9

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-09-21 21:59 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-21 21:59 [rpms/ibus-speech-to-text] rawhide: update to release 1.1.0 matiwari

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox