From 4fad897f141b197e937b87dcfe0563e1f6dff560 Mon Sep 17 00:00:00 2001 From: Jatin K Malik Date: Mon, 27 Jul 2026 19:57:07 -0700 Subject: [PATCH] fix(appimage): smoke-test fixes for tray, labels, and packaging (#602) * fix(ui): AppImage smoke fixes for tray icons, labels, and identity Replace placeholder mic tray SVGs with opaque branded icons, show whisper.cpp instead of Whisper_cpp, set GLib/AppIndicator identity to Vocalinux (not main.py), and give About dialog buttons enough padding so labels are not clipped under Adwaita. * fix(packaging): harden AppImage build for Wayland and tray deps Pin the build interpreter, install onnxruntime into the bundle, set desktop Exec=AppRun + StartupWMClass, prune typelibs to an allowlist after linuxdeploy, and stop forcing GDK_BACKEND=x11 on Wayland (VOCALINUX_FORCE_X11=1 remains as an escape hatch). CI AppImage jobs also pull Ayatana AppIndicator and librsvg SVG loader packages. * fix(packaging): bundle full stdlib from base_prefix in AppImage When build.sh runs under a venv, sys.prefix only has site-packages, so the AppImage was missing encodings and crashed on start. Copy the interpreter and stdlib from sys.base_prefix, force pip --ignore-installed into AppDir, set Gdk program class to Vocalinux, and tone down the muted tray slash so small StatusNotifier icons stay slate. * fix(tray): use absolute icon paths so stale hicolor icons lose XFCE StatusNotifier resolves IconName through the system theme and can ignore IconThemePath. A leftover ~/.local/share/icons placeholder (vocalinux-microphone-off.svg filled #e74c3c) was winning over the AppImage's bundled icons. Outside Flatpak, pass absolute SVG paths to set_icon_full / new_with_path instead of bare theme names. --- .github/workflows/nightly.yml | 10 +- .github/workflows/release.yml | 10 +- .github/workflows/unified-pipeline.yml | 3 + packaging/appimage/build.sh | 106 +++++++++++++++--- .../scalable/vocalinux-microphone-off.svg | 16 ++- .../scalable/vocalinux-microphone-process.svg | 26 ++--- .../icons/scalable/vocalinux-microphone.svg | 20 ++-- src/vocalinux/main.py | 16 +++ .../scalable/vocalinux-microphone-off.svg | 16 ++- .../scalable/vocalinux-microphone-process.svg | 26 ++--- .../icons/scalable/vocalinux-microphone.svg | 20 ++-- src/vocalinux/ui/about_dialog.py | 5 + src/vocalinux/ui/settings_dialog.py | 5 +- src/vocalinux/ui/tray_indicator.py | 23 +++- tests/test_engine_display_names.py | 18 +++ tests/test_tray_indicator.py | 13 ++- web/public/vocalinux-microphone-off.svg | 16 ++- web/public/vocalinux-microphone-process.svg | 26 ++--- web/public/vocalinux-microphone.svg | 20 ++-- 19 files changed, 282 insertions(+), 113 deletions(-) create mode 100644 tests/test_engine_display_names.py diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ad87f93..305048e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -42,7 +42,10 @@ jobs: gobject-introspection \ gir1.2-gtk-3.0 \ gir1.2-appindicator3-0.1 \ - gir1.2-ibus-1.0 + gir1.2-ayatanaappindicator3-0.1 \ + gir1.2-ibus-1.0 \ + gir1.2-rsvg-2.0 \ + librsvg2-common - name: Install build dependencies run: | @@ -284,7 +287,10 @@ jobs: gobject-introspection \ gir1.2-gtk-3.0 \ gir1.2-appindicator3-0.1 \ - gir1.2-ibus-1.0 + gir1.2-ayatanaappindicator3-0.1 \ + gir1.2-ibus-1.0 \ + gir1.2-rsvg-2.0 \ + librsvg2-common - name: Install build dependencies run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 30c152e..0125ab8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,7 +42,10 @@ jobs: gobject-introspection \ gir1.2-gtk-3.0 \ gir1.2-appindicator3-0.1 \ - gir1.2-ibus-1.0 + gir1.2-ayatanaappindicator3-0.1 \ + gir1.2-ibus-1.0 \ + gir1.2-rsvg-2.0 \ + librsvg2-common - name: Install build dependencies run: | @@ -163,7 +166,10 @@ jobs: gobject-introspection \ gir1.2-gtk-3.0 \ gir1.2-appindicator3-0.1 \ - gir1.2-ibus-1.0 + gir1.2-ayatanaappindicator3-0.1 \ + gir1.2-ibus-1.0 \ + gir1.2-rsvg-2.0 \ + librsvg2-common - name: Install build dependencies run: | diff --git a/.github/workflows/unified-pipeline.yml b/.github/workflows/unified-pipeline.yml index 5c1a194..3b3e4af 100644 --- a/.github/workflows/unified-pipeline.yml +++ b/.github/workflows/unified-pipeline.yml @@ -198,7 +198,10 @@ jobs: sudo apt-get install -y \ xdotool \ gir1.2-appindicator3-0.1 \ + gir1.2-ayatanaappindicator3-0.1 \ gir1.2-ibus-1.0 \ + gir1.2-rsvg-2.0 \ + librsvg2-common \ libcairo2-dev \ pkg-config \ python3-dev \ diff --git a/packaging/appimage/build.sh b/packaging/appimage/build.sh index 61e5066..0d1975e 100755 --- a/packaging/appimage/build.sh +++ b/packaging/appimage/build.sh @@ -14,6 +14,9 @@ # ponytail: text-injection CLI tools (xdotool/wtype/ydotool) are not # bundled, same runtime prerequisite as the PyPI install path documented # in docs/INSTALL.md. Add bundling if users hit missing-binary complaints. +# +# GPU note: the pip pywhispercpp wheel is CPU-only. Vulkan/CUDA rebuilds +# stay on install.sh for now; the AppImage logs when GPU libs are absent. set -euo pipefail WHEEL="$1" @@ -21,18 +24,28 @@ VERSION="$2" OUTDIR="${3:-dist}" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" ARCH="$(uname -m)" +PYTHON="${PYTHON:-python3}" case "$ARCH" in x86_64|aarch64) ;; *) echo "Unsupported architecture: $ARCH (need x86_64 or aarch64)" >&2; exit 1 ;; esac +TYPELIBS=( + Gtk-3.0 Gdk-3.0 GdkX11-3.0 GdkPixbuf-2.0 GLib-2.0 GObject-2.0 Gio-2.0 + Pango-1.0 PangoCairo-1.0 cairo-1.0 HarfBuzz-0.0 Atk-1.0 freetype2-2.0 + AppIndicator3-0.1 AyatanaAppIndicator3-0.1 Notify-0.7 IBus-1.0 Rsvg-2.0 +) + WORKDIR="$(mktemp -d)" trap 'rm -rf "$WORKDIR"' EXIT APPDIR="$WORKDIR/AppDir" TOOLDIR="$WORKDIR/tools" mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/lib" "$TOOLDIR" "$OUTDIR" +# Nested AppImages need extract-and-run on hosts without usable FUSE. +export APPIMAGE_EXTRACT_AND_RUN="${APPIMAGE_EXTRACT_AND_RUN:-1}" + echo "== Fetching AppImage tooling ($ARCH) ==" curl -fsSL -o "$TOOLDIR/linuxdeploy" \ "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-${ARCH}.AppImage" @@ -42,31 +55,52 @@ curl -fsSL -o "$TOOLDIR/appimagetool" \ "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${ARCH}.AppImage" chmod +x "$TOOLDIR/linuxdeploy" "$TOOLDIR/linuxdeploy-plugin-gtk.sh" "$TOOLDIR/appimagetool" -echo "== Bundling Python runtime ==" -PY_PREFIX="$(python3 -c 'import sys; print(sys.prefix)')" -PY_VER="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')" -cp -L "$(command -v python3)" "$APPDIR/usr/bin/python3" +echo "== Bundling Python runtime ($PYTHON) ==" +# Use base_prefix so a venv builder still ships a full stdlib (encodings, etc.). +PY_PREFIX="$("$PYTHON" -c 'import sys; print(sys.base_prefix)')" +PY_VER="$("$PYTHON" -c 'import sys; print("%d.%d" % sys.version_info[:2])')" +PY_BIN="$("$PYTHON" -c 'import sys; print(sys.base_prefix)')/bin/python${PY_VER}" +if [ ! -x "$PY_BIN" ]; then + PY_BIN="$("$PYTHON" -c 'import sys; print(sys.base_prefix)')/bin/python3" +fi +if [ ! -x "$PY_BIN" ]; then + PY_BIN="$("$PYTHON" -c 'import sys; print(sys.executable)')" +fi +cp -L "$PY_BIN" "$APPDIR/usr/bin/python3" cp -r "$PY_PREFIX/lib/python${PY_VER}" "$APPDIR/usr/lib/python${PY_VER}" rm -rf "$APPDIR/usr/lib/python${PY_VER}/site-packages" -echo "== Installing Vocalinux + PyGObject into the bundle ==" -python3 -m pip install --no-cache-dir --prefix "$APPDIR/usr" "$WHEEL" PyGObject pycairo +echo "== Installing Vocalinux + runtime deps into the bundle ==" +# --ignore-installed: pip otherwise treats the builder env's packages as +# satisfying deps and skips copying vosk/pywhispercpp/etc. into AppDir. +"$PYTHON" -m pip install --no-cache-dir --ignore-installed --prefix "$APPDIR/usr" \ + "$WHEEL" PyGObject pycairo onnxruntime echo "== Adding desktop entry + icon ==" install -Dm644 "$REPO_ROOT/vocalinux.desktop" "$APPDIR/usr/share/applications/vocalinux.desktop" +# AppImage desktop integration expects Exec=AppRun; set WM class for the tray app. +sed -i \ + -e 's|^Exec=.*|Exec=AppRun|' \ + -e '/^StartupWMClass=/d' \ + "$APPDIR/usr/share/applications/vocalinux.desktop" +printf 'StartupWMClass=vocalinux\n' >> "$APPDIR/usr/share/applications/vocalinux.desktop" install -Dm644 "$REPO_ROOT/resources/icons/scalable/vocalinux.svg" \ "$APPDIR/usr/share/icons/hicolor/scalable/apps/vocalinux.svg" +copy_typelibs() { + local dest="$1" + mkdir -p "$dest" + local typelib found + for typelib in "${TYPELIBS[@]}"; do + found="$(find /usr/lib -name "${typelib}.typelib" 2>/dev/null | head -1 || true)" + if [ -n "$found" ]; then + cp "$found" "$dest/" + fi + done +} + echo "== Copying GObject-Introspection typelibs (not handled by linuxdeploy-plugin-gtk) ==" -mkdir -p "$APPDIR/usr/lib/girepository-1.0" -for typelib in Gtk-3.0 Gdk-3.0 GdkX11-3.0 GdkPixbuf-2.0 GLib-2.0 GObject-2.0 Gio-2.0 \ - Pango-1.0 PangoCairo-1.0 cairo-1.0 HarfBuzz-0.0 Atk-1.0 freetype2-2.0 \ - AppIndicator3-0.1 AyatanaAppIndicator3-0.1 Notify-0.7 IBus-1.0; do - found="$(find /usr/lib -name "${typelib}.typelib" 2>/dev/null | head -1)" - if [ -n "$found" ]; then - cp "$found" "$APPDIR/usr/lib/girepository-1.0/" - fi -done +copy_typelibs "$APPDIR/usr/lib/girepository-1.0" echo "== Writing AppRun ==" cat > "$APPDIR/AppRun" << 'APPRUN' @@ -81,7 +115,7 @@ exec "$HERE/usr/bin/python3" -m vocalinux.main "$@" APPRUN # PYTHONPATH above uses a version-agnostic symlink so AppRun doesn't need # to know the exact interpreter minor version at runtime. -ln -s "python${PY_VER}" "$APPDIR/usr/lib/python3" +ln -sfn "python${PY_VER}" "$APPDIR/usr/lib/python3" chmod +x "$APPDIR/AppRun" echo "== Running linuxdeploy (resolves the shared-library closure + GTK theming) ==" @@ -92,6 +126,46 @@ export DEPLOY_GTK_VERSION=3 -d "$APPDIR/usr/share/applications/vocalinux.desktop" \ -i "$APPDIR/usr/share/icons/hicolor/scalable/apps/vocalinux.svg" +echo "== Pruning typelibs back to the allowlist (linuxdeploy copies the host set) ==" +rm -rf "$APPDIR/usr/lib/girepository-1.0" +copy_typelibs "$APPDIR/usr/lib/girepository-1.0" + +echo "== Patching linuxdeploy GTK AppRun hook (Wayland-friendly GDK backend) ==" +GTK_HOOK="$APPDIR/apprun-hooks/linuxdeploy-plugin-gtk.sh" +if [ -f "$GTK_HOOK" ]; then + # Upstream forces GDK_BACKEND=x11 even on Wayland, which breaks hover/focus + # for GTK widgets under Plasma (XWayland). Prefer native Wayland unless the + # user opts into X11 via VOCALINUX_FORCE_X11=1. + python3 - "$GTK_HOOK" <<'PY' +import pathlib, re, sys +path = pathlib.Path(sys.argv[1]) +text = path.read_text() +replacement = ( + '# Prefer Wayland when available; force X11 only when requested.\n' + 'if [ "${VOCALINUX_FORCE_X11:-0}" = "1" ]; then\n' + ' export GDK_BACKEND=x11\n' + 'elif [ -n "${WAYLAND_DISPLAY:-}" ] || [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then\n' + ' export GDK_BACKEND=wayland\n' + 'else\n' + ' export GDK_BACKEND=x11\n' + 'fi' +) +new, n = re.subn( + r'^[ \t]*export GDK_BACKEND=x11[^\n]*$', + replacement, + text, + count=1, + flags=re.M, +) +if n != 1: + raise SystemExit(f"failed to patch GDK_BACKEND in {path} (matches={n})") +path.write_text(new) +print(f"Patched {path}") +PY +else + echo "Warning: GTK AppRun hook not found at $GTK_HOOK" >&2 +fi + echo "== Packaging AppImage ==" OUTPUT="$OUTDIR/Vocalinux-${VERSION}-${ARCH}.AppImage" ARCH="$ARCH" "$TOOLDIR/appimagetool" "$APPDIR" "$OUTPUT" diff --git a/resources/icons/scalable/vocalinux-microphone-off.svg b/resources/icons/scalable/vocalinux-microphone-off.svg index a5f8b2f..c7ec58b 100644 --- a/resources/icons/scalable/vocalinux-microphone-off.svg +++ b/resources/icons/scalable/vocalinux-microphone-off.svg @@ -1,8 +1,12 @@ - - - - - - + + + + + + + + + + diff --git a/resources/icons/scalable/vocalinux-microphone-process.svg b/resources/icons/scalable/vocalinux-microphone-process.svg index 6fa2d08..6bc3572 100644 --- a/resources/icons/scalable/vocalinux-microphone-process.svg +++ b/resources/icons/scalable/vocalinux-microphone-process.svg @@ -1,16 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/resources/icons/scalable/vocalinux-microphone.svg b/resources/icons/scalable/vocalinux-microphone.svg index a3f86d0..33225bf 100644 --- a/resources/icons/scalable/vocalinux-microphone.svg +++ b/resources/icons/scalable/vocalinux-microphone.svg @@ -1,9 +1,15 @@ - - - - - - - + + + + + + + + + + + + + diff --git a/src/vocalinux/main.py b/src/vocalinux/main.py index 45c9225..eb3362e 100644 --- a/src/vocalinux/main.py +++ b/src/vocalinux/main.py @@ -263,6 +263,22 @@ def main(): logger.error("Cannot start Vocalinux due to missing dependencies") sys.exit(1) + # Identity for WM class / AppIndicator title (otherwise shows as main.py) + import gi + + gi.require_version("GLib", "2.0") + from gi.repository import GLib + + GLib.set_prgname("vocalinux") + GLib.set_application_name("Vocalinux") + try: + gi.require_version("Gdk", "3.0") + from gi.repository import Gdk + + Gdk.set_program_class("Vocalinux") + except Exception: + pass + # Check if display is available before creating any GTK widgets if not check_display_available(): sys.exit(1) diff --git a/src/vocalinux/resources/icons/scalable/vocalinux-microphone-off.svg b/src/vocalinux/resources/icons/scalable/vocalinux-microphone-off.svg index a5f8b2f..c7ec58b 100644 --- a/src/vocalinux/resources/icons/scalable/vocalinux-microphone-off.svg +++ b/src/vocalinux/resources/icons/scalable/vocalinux-microphone-off.svg @@ -1,8 +1,12 @@ - - - - - - + + + + + + + + + + diff --git a/src/vocalinux/resources/icons/scalable/vocalinux-microphone-process.svg b/src/vocalinux/resources/icons/scalable/vocalinux-microphone-process.svg index 6fa2d08..6bc3572 100644 --- a/src/vocalinux/resources/icons/scalable/vocalinux-microphone-process.svg +++ b/src/vocalinux/resources/icons/scalable/vocalinux-microphone-process.svg @@ -1,16 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/vocalinux/resources/icons/scalable/vocalinux-microphone.svg b/src/vocalinux/resources/icons/scalable/vocalinux-microphone.svg index a3f86d0..33225bf 100644 --- a/src/vocalinux/resources/icons/scalable/vocalinux-microphone.svg +++ b/src/vocalinux/resources/icons/scalable/vocalinux-microphone.svg @@ -1,9 +1,15 @@ - - - - - - - + + + + + + + + + + + + + diff --git a/src/vocalinux/ui/about_dialog.py b/src/vocalinux/ui/about_dialog.py index 2ee2103..962b2a9 100644 --- a/src/vocalinux/ui/about_dialog.py +++ b/src/vocalinux/ui/about_dialog.py @@ -186,6 +186,11 @@ class AboutDialog(Gtk.Dialog): color: @theme_unfocused_fg_color; font-size: 0.85em; } + + .about-dialog button { + min-height: 32px; + padding: 6px 14px; + } """ def __init__(self, parent: Gtk.Window = None): diff --git a/src/vocalinux/ui/settings_dialog.py b/src/vocalinux/ui/settings_dialog.py index 7e615cd..61c6e4a 100644 --- a/src/vocalinux/ui/settings_dialog.py +++ b/src/vocalinux/ui/settings_dialog.py @@ -89,7 +89,7 @@ ENGINE_MODELS = { ENGINE_DISPLAY_NAMES = { "vosk": "Vosk", "whisper": "Whisper", - "whisper_cpp": "Whisper_cpp", + "whisper_cpp": "whisper.cpp", "remote_api": "Remote API", } @@ -3657,8 +3657,7 @@ class SettingsDialog(Gtk.Dialog): def _update_advanced_tab_sensitivity(self): """Enable or disable advanced settings based on selected engine.""" - engine_text = self.engine_combo.get_active_text() - is_whispercpp = engine_text and engine_text.lower() == "whisper_cpp" + is_whispercpp = self._get_selected_engine() == "whisper_cpp" widgets = [ self.advanced_no_timestamps_switch, diff --git a/src/vocalinux/ui/tray_indicator.py b/src/vocalinux/ui/tray_indicator.py index 246f2f8..c225266 100644 --- a/src/vocalinux/ui/tray_indicator.py +++ b/src/vocalinux/ui/tray_indicator.py @@ -117,6 +117,18 @@ class TrayIndicator: } self.icon_names = _themed_icon_names() + # Prefer absolute icon paths outside Flatpak. StatusNotifier hosts often + # resolve IconName via the system hicolor theme and ignore IconThemePath, + # so a stale ~/.local/share/icons copy of vocalinux-microphone-off.svg + # (the old red placeholder) would win over the AppImage's bundled icons. + self._icon_keys = { + "default": (self.icon_names["default"] if FLATPAK_ID else self.icon_paths["default"]), + "active": (self.icon_names["active"] if FLATPAK_ID else self.icon_paths["active"]), + "processing": ( + self.icon_names["processing"] if FLATPAK_ID else self.icon_paths["processing"] + ), + } + # Register for speech recognition state changes self.speech_engine.register_state_callback(self._on_recognition_state_changed) @@ -222,7 +234,7 @@ class TrayIndicator: exists = os.path.exists(path) logger.info(f"Icon '{name}' ({path}): {'exists' if exists else 'missing'}") - initial_icon = _themed_icon_names()["default"] + initial_icon = self._icon_keys["default"] try: if FLATPAK_ID: self.indicator = AppIndicator3.Indicator.new( @@ -239,6 +251,7 @@ class TrayIndicator: ) self.indicator.set_icon_theme_path(ICON_DIR) self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE) + self.indicator.set_title("Vocalinux") except Exception as e: logger.error(f"Failed to create AppIndicator: {e}") GLib.idle_add(self._show_appindicator_error_dialog, str(e)) @@ -463,19 +476,19 @@ class TrayIndicator: return False if state == RecognitionState.IDLE: - self.indicator.set_icon_full(self.icon_names["default"], "Microphone off") + self.indicator.set_icon_full(self._icon_keys["default"], "Microphone off") self._set_menu_item_enabled("Start Voice Typing", True) self._set_menu_item_enabled("Stop Voice Typing", False) elif state == RecognitionState.LISTENING: - self.indicator.set_icon_full(self.icon_names["active"], "Microphone on") + self.indicator.set_icon_full(self._icon_keys["active"], "Microphone on") self._set_menu_item_enabled("Start Voice Typing", False) self._set_menu_item_enabled("Stop Voice Typing", True) elif state == RecognitionState.PROCESSING: - self.indicator.set_icon_full(self.icon_names["processing"], "Processing speech") + self.indicator.set_icon_full(self._icon_keys["processing"], "Processing speech") self._set_menu_item_enabled("Start Voice Typing", False) self._set_menu_item_enabled("Stop Voice Typing", True) elif state == RecognitionState.ERROR: - self.indicator.set_icon_full(self.icon_names["default"], "Error") + self.indicator.set_icon_full(self._icon_keys["default"], "Error") self._set_menu_item_enabled("Start Voice Typing", True) self._set_menu_item_enabled("Stop Voice Typing", False) diff --git a/tests/test_engine_display_names.py b/tests/test_engine_display_names.py new file mode 100644 index 0000000..bda5d0f --- /dev/null +++ b/tests/test_engine_display_names.py @@ -0,0 +1,18 @@ +"""Tests for speech-engine display name helpers.""" + +from vocalinux.ui.settings_dialog import ( + ENGINE_DISPLAY_NAMES, + _engine_display_name, + _engine_from_display, +) + + +def test_whisper_cpp_display_name(): + assert ENGINE_DISPLAY_NAMES["whisper_cpp"] == "whisper.cpp" + assert _engine_display_name("whisper_cpp") == "whisper.cpp" + assert _engine_from_display("whisper.cpp") == "whisper_cpp" + + +def test_engine_display_roundtrip(): + for engine_id in ENGINE_DISPLAY_NAMES: + assert _engine_from_display(_engine_display_name(engine_id)) == engine_id diff --git a/tests/test_tray_indicator.py b/tests/test_tray_indicator.py index baaab71..6f0f745 100644 --- a/tests/test_tray_indicator.py +++ b/tests/test_tray_indicator.py @@ -323,7 +323,7 @@ class TestTrayIndicator(unittest.TestCase): result = self.tray_indicator._update_ui(self.RecognitionState.LISTENING) self.tray_indicator.indicator.set_icon_full.assert_called_once_with( - "vocalinux-microphone", "Microphone on" + self.tray_indicator._icon_keys["active"], "Microphone on" ) self.assertEqual(result, False) @@ -340,7 +340,7 @@ class TestTrayIndicator(unittest.TestCase): result = self.tray_indicator._update_ui(self.RecognitionState.PROCESSING) self.tray_indicator.indicator.set_icon_full.assert_called_once_with( - "vocalinux-microphone-process", "Processing speech" + self.tray_indicator._icon_keys["processing"], "Processing speech" ) self.assertEqual(result, False) @@ -357,7 +357,7 @@ class TestTrayIndicator(unittest.TestCase): result = self.tray_indicator._update_ui(self.RecognitionState.ERROR) self.tray_indicator.indicator.set_icon_full.assert_called_once_with( - "vocalinux-microphone-off", "Error" + self.tray_indicator._icon_keys["default"], "Error" ) self.assertEqual(result, False) @@ -515,7 +515,12 @@ class TestTrayIndicator(unittest.TestCase): from vocalinux.ui.tray_indicator import TrayIndicator tray = TrayIndicator.__new__(TrayIndicator) - tray.icon_paths = {} + tray.icon_paths = { + "default": "/tmp/fake-off.svg", + "active": "/tmp/fake-on.svg", + "processing": "/tmp/fake-process.svg", + } + tray._icon_keys = dict(tray.icon_paths) tray._show_appindicator_error_dialog = MagicMock(return_value=False) with patch("vocalinux.ui.tray_indicator.os.path.exists", return_value=False): diff --git a/web/public/vocalinux-microphone-off.svg b/web/public/vocalinux-microphone-off.svg index a5f8b2f..c7ec58b 100644 --- a/web/public/vocalinux-microphone-off.svg +++ b/web/public/vocalinux-microphone-off.svg @@ -1,8 +1,12 @@ - - - - - - + + + + + + + + + + diff --git a/web/public/vocalinux-microphone-process.svg b/web/public/vocalinux-microphone-process.svg index 6fa2d08..6bc3572 100644 --- a/web/public/vocalinux-microphone-process.svg +++ b/web/public/vocalinux-microphone-process.svg @@ -1,16 +1,14 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/web/public/vocalinux-microphone.svg b/web/public/vocalinux-microphone.svg index a3f86d0..33225bf 100644 --- a/web/public/vocalinux-microphone.svg +++ b/web/public/vocalinux-microphone.svg @@ -1,9 +1,15 @@ - - - - - - - + + + + + + + + + + + + +