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.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
|
||||
<!-- Placeholder for microphone off icon -->
|
||||
<circle cx="64" cy="64" r="60" fill="#e74c3c" />
|
||||
<rect x="44" y="40" width="40" height="60" rx="20" fill="#7f8c8d" />
|
||||
<line x1="40" y1="88" x2="88" y2="40" stroke="#e74c3c" stroke-width="8" />
|
||||
<!-- This is a placeholder. Replace with actual custom design. -->
|
||||
<!-- Idle / muted: slate disc; keep slash subtle so 22px trays don't look like a red badge -->
|
||||
<circle cx="64" cy="64" r="60" fill="#1e293b"/>
|
||||
<!-- Mic body -->
|
||||
<rect x="52" y="28" width="24" height="40" rx="12" fill="#94a3b8"/>
|
||||
<!-- Mic stand -->
|
||||
<path d="M40 68c0 13.255 10.745 24 24 24s24-10.745 24-24" fill="none" stroke="#94a3b8" stroke-width="6" stroke-linecap="round"/>
|
||||
<line x1="64" y1="92" x2="64" y2="102" stroke="#94a3b8" stroke-width="6" stroke-linecap="round"/>
|
||||
<line x1="50" y1="102" x2="78" y2="102" stroke="#94a3b8" stroke-width="6" stroke-linecap="round"/>
|
||||
<!-- Mute slash: light outline only so the slate disc stays the dominant color -->
|
||||
<line x1="36" y1="96" x2="92" y2="32" stroke="#f8fafc" stroke-width="8" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 465 B After Width: | Height: | Size: 869 B |
@@ -1,16 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
|
||||
<!-- Placeholder for microphone processing icon -->
|
||||
<circle cx="64" cy="64" r="60" fill="#f39c12" />
|
||||
<rect x="44" y="40" width="40" height="60" rx="20" fill="#2c3e50" />
|
||||
<circle cx="64" cy="40" r="8" fill="#f39c12">
|
||||
<animate attributeName="cy" values="40;50;40" dur="1s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="48" cy="60" r="8" fill="#f39c12">
|
||||
<animate attributeName="cy" values="60;70;60" dur="1s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="80" cy="60" r="8" fill="#f39c12">
|
||||
<animate attributeName="cy" values="60;70;60" dur="1s" repeatCount="indefinite" />
|
||||
</circle>
|
||||
<!-- This is a placeholder. Replace with actual custom design. -->
|
||||
<!-- Processing: amber disc, opaque for StatusNotifier trays -->
|
||||
<circle cx="64" cy="64" r="60" fill="#d97706"/>
|
||||
<!-- Mic body -->
|
||||
<rect x="52" y="28" width="24" height="40" rx="12" fill="#1c1917"/>
|
||||
<!-- Mic stand -->
|
||||
<path d="M40 68c0 13.255 10.745 24 24 24s24-10.745 24-24" fill="none" stroke="#1c1917" stroke-width="6" stroke-linecap="round"/>
|
||||
<line x1="64" y1="92" x2="64" y2="102" stroke="#1c1917" stroke-width="6" stroke-linecap="round"/>
|
||||
<line x1="50" y1="102" x2="78" y2="102" stroke="#1c1917" stroke-width="6" stroke-linecap="round"/>
|
||||
<!-- Processing dots -->
|
||||
<circle cx="44" cy="52" r="6" fill="#fffbeb"/>
|
||||
<circle cx="64" cy="44" r="6" fill="#fffbeb"/>
|
||||
<circle cx="84" cy="52" r="6" fill="#fffbeb"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 829 B |
@@ -1,9 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
|
||||
<!-- Placeholder for microphone on/active icon -->
|
||||
<circle cx="64" cy="64" r="60" fill="#2ecc71" />
|
||||
<rect x="44" y="40" width="40" height="60" rx="20" fill="#2c3e50" />
|
||||
<path d="M 40,78 Q 64,88 88,78" stroke="#2ecc71" stroke-width="4" fill="none" />
|
||||
<path d="M 40,88 Q 64,98 88,88" stroke="#2ecc71" stroke-width="4" fill="none" />
|
||||
<!-- This is a placeholder. Replace with actual custom design. -->
|
||||
<!-- Active / listening: branded teal disc, opaque for StatusNotifier trays -->
|
||||
<circle cx="64" cy="64" r="60" fill="#0d9488"/>
|
||||
<!-- Mic body -->
|
||||
<rect x="52" y="28" width="24" height="40" rx="12" fill="#0f172a"/>
|
||||
<!-- Mic stand -->
|
||||
<path d="M40 68c0 13.255 10.745 24 24 24s24-10.745 24-24" fill="none" stroke="#0f172a" stroke-width="6" stroke-linecap="round"/>
|
||||
<line x1="64" y1="92" x2="64" y2="102" stroke="#0f172a" stroke-width="6" stroke-linecap="round"/>
|
||||
<line x1="50" y1="102" x2="78" y2="102" stroke="#0f172a" stroke-width="6" stroke-linecap="round"/>
|
||||
<!-- Sound waves -->
|
||||
<path d="M86 48c4 4 6 9 6 16s-2 12-6 16" fill="none" stroke="#ecfdf5" stroke-width="5" stroke-linecap="round"/>
|
||||
<path d="M96 40c7 7 11 15 11 24s-4 17-11 24" fill="none" stroke="#ecfdf5" stroke-width="5" stroke-linecap="round"/>
|
||||
<path d="M42 48c-4 4-6 9-6 16s2 12 6 16" fill="none" stroke="#ecfdf5" stroke-width="5" stroke-linecap="round"/>
|
||||
<path d="M32 40c-7 7-11 15-11 24s4 17 11 24" fill="none" stroke="#ecfdf5" stroke-width="5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 560 B After Width: | Height: | Size: 1.1 KiB |
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user