feat(identity): rename user-visible identity to voculinux with one-time legacy import

The fork takes its own name everywhere a user can see it: binaries
(voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus
namespace (com.voculinux.Voculinux, no alias for the old name),
config/data/cache dirs, single-instance lock, IBus engine identity,
tray icons, resource prefixes, VOCULINUX_* environment names, and the
update-checker user agent. The internal Python module stays vocalinux
and the distribution metadata keeps the upstream URLs as provenance.

On first run a previous Vocalinux install's config.json, custom
dictionary, and portal restore token are copied once into the new
locations (new config always wins, no merge, no write-back); models
re-resolve through the pinned-checksum download path. An offline
naming-sweep test proves no old-name strings remain on user-visible
surfaces outside the allowlist.

Refs #18
This commit is contained in:
soubarnak
2026-11-06 01:29:39 +05:30
parent c565e6d946
commit ea7be5f111
251 changed files with 2449 additions and 1991 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Vocalinux - A seamless voice dictation system for Linux
Voculinux - A seamless voice dictation system for Linux
Note: GTK-dependent modules are imported lazily in main.py to allow
the package to be imported even when system GTK typelibs are not available.
+47 -41
View File
@@ -1,68 +1,74 @@
"""Central user-visible application identity for Vocalinux.
"""Central user-visible application identity for Voculinux.
Ticket #17 prefactor: every user-visible identity string that is hardcoded in
more than one place is driven from this single module. No value changes here;
the rename ticket (#18) flips them in one place instead of a scavenger hunt.
Ticket #18 rename: every user-visible identity string that is hardcoded in
more than one place is driven from this single module. The fork takes its
own name everywhere a user can see it; the internal ``voculinux`` Python
package name is retained (see :data:`RETAINED_SURFACES`) and unrelated to
these values.
Retained internal names (the ``vocalinux`` Python package, upstream URLs, and
changelog history) are recorded in :data:`RETAINED_SURFACES`, not renamed.
One-time legacy import of a previous Voculinux install lives in
:mod:`vocalinux.legacy_import` and keys off :data:`LEGACY_APP_DIR_NAME`.
"""
APP_NAME: str = "Vocalinux"
APP_ID: str = "vocalinux"
APP_DIR_NAME: str = "vocalinux"
CLI_PROG: str = "vocalinux"
APP_NAME: str = "Voculinux"
APP_ID: str = "voculinux"
APP_DIR_NAME: str = "voculinux"
CLI_PROG: str = "voculinux"
BUS_NAME: str = "com.vocalinux.Vocalinux"
OBJECT_PATH: str = "/com/vocalinux/Vocalinux"
INTERFACE_NAME: str = "com.vocalinux.Vocalinux"
BUS_NAME: str = "com.voculinux.Voculinux"
OBJECT_PATH: str = "/com/voculinux/Voculinux"
INTERFACE_NAME: str = "com.voculinux.Voculinux"
DESKTOP_FILENAME: str = "vocalinux.desktop"
DESKTOP_ICON_NAME: str = "vocalinux"
DESKTOP_FILENAME: str = "voculinux.desktop"
DESKTOP_ICON_NAME: str = "voculinux"
TRAY_TITLE: str = APP_NAME
ICON_DEFAULT: str = "vocalinux-microphone-off"
ICON_ACTIVE: str = "vocalinux-microphone"
ICON_PROCESSING: str = "vocalinux-microphone-process"
ICON_DEFAULT: str = "voculinux-microphone-off"
ICON_ACTIVE: str = "voculinux-microphone"
ICON_PROCESSING: str = "voculinux-microphone-process"
ICON_BASE_NAMES: tuple[str, ...] = (
"vocalinux",
"voculinux",
ICON_ACTIVE,
ICON_DEFAULT,
ICON_PROCESSING,
)
ENGINE_NAME: str = "vocalinux"
ENGINE_LONGNAME: str = "Vocalinux"
ENGINE_DESCRIPTION: str = "Vocalinux voice dictation (use as default input method)"
COMPONENT_NAME: str = "org.freedesktop.IBus.Vocalinux"
IBUS_DIR_NAME: str = "vocalinux-ibus"
IBUS_TEXTDOMAIN: str = "vocalinux"
ENGINE_NAME: str = "voculinux"
ENGINE_LONGNAME: str = "Voculinux"
ENGINE_DESCRIPTION: str = "Voculinux voice dictation (use as default input method)"
COMPONENT_NAME: str = "org.freedesktop.IBus.Voculinux"
IBUS_DIR_NAME: str = "voculinux-ibus"
IBUS_TEXTDOMAIN: str = "voculinux"
UPDATE_CHECKER_USER_AGENT: str = "Vocalinux-UpdateChecker"
UPDATE_CHECKER_USER_AGENT: str = "Voculinux-UpdateChecker"
RESOURCE_DIR_NAME: str = "vocalinux"
RESOURCE_DIR_NAME: str = "voculinux"
VERIFICATION_STAMP_NAME: str = ".vocalinux_verified"
LOG_FILE_PREFIX: str = "vocalinux_logs_"
PORTAL_TOKEN_PREFIX: str = "vocalinux_"
VERIFICATION_STAMP_NAME: str = ".voculinux_verified"
LOG_FILE_PREFIX: str = "voculinux_logs_"
PORTAL_TOKEN_PREFIX: str = "voculinux_"
ENV_FORCE_BACKEND: str = "VOCALINUX_FORCE_BACKEND"
ENV_INJECT_MODIFIER_WAIT: str = "VOCALINUX_INJECT_MODIFIER_WAIT"
ENV_YDOTOOL_KEY_DELAY: str = "VOCALINUX_YDOTOOL_KEY_DELAY"
ENV_FORCE_BACKEND: str = "VOCULINUX_FORCE_BACKEND"
ENV_INJECT_MODIFIER_WAIT: str = "VOCULINUX_INJECT_MODIFIER_WAIT"
ENV_YDOTOOL_KEY_DELAY: str = "VOCULINUX_YDOTOOL_KEY_DELAY"
SITE_URL: str = "https://vocalinux.com"
SITE_URL: str = "https://voculinux.com"
#: Files the rename ticket (#18) must flip. Checked by
#: Previous install locations read once by the legacy importer. Never written.
LEGACY_APP_DIR_NAME: str = "vocalinux"
LEGACY_BUS_NAME: str = "com.vocalinux.Vocalinux"
#: Files the rename ticket (#18) flipped. Checked by
#: ``tests/test_app_identity.py`` so the list cannot drift silently.
RENAME_SURFACES: tuple[str, ...] = (
"pyproject.toml",
"vocalinux.desktop",
"snap/gui/vocalinux.desktop",
"voculinux.desktop",
"snap/gui/voculinux.desktop",
"snap/snapcraft.yaml",
"packaging/flatpak/com.vocalinux.Vocalinux.desktop",
"packaging/flatpak/com.vocalinux.Vocalinux.metainfo.xml",
"packaging/flatpak/com.vocalinux.Vocalinux.yml",
"packaging/aur/vocalinux-bin/vocalinux.desktop",
"packaging/flatpak/com.voculinux.Voculinux.desktop",
"packaging/flatpak/com.voculinux.Voculinux.metainfo.xml",
"packaging/flatpak/com.voculinux.Voculinux.yml",
"packaging/aur/voculinux-bin/voculinux.desktop",
"web/src/app/page.tsx",
"web/package.json",
"README.md",
+1 -1
View File
@@ -494,7 +494,7 @@ def _open_capture_stream(audio: Any, device_index: Optional[int] = None) -> tupl
"""
Negotiate a working (channels, sample_rate) and return the opened stream.
Historically Vocalinux probed channels and sample rates separately, each
Historically Voculinux probed channels and sample rates separately, each
opening and closing PortAudio streams, then opened the real capture stream
on top — three open/close cycles in under a second. Bluetooth headset
capture (SCO/HFP / PipeWire "Bluetooth internal capture stream") is
+4 -4
View File
@@ -1,9 +1,9 @@
"""
Session-bus D-Bus service for Vocalinux.
Session-bus D-Bus service for Voculinux.
Exposes ``com.vocalinux.Vocalinux`` with ``Toggle``/``Start``/``Stop`` methods
Exposes ``com.voculinux.Voculinux`` with ``Toggle``/``Start``/``Stop`` methods
so an external trigger (e.g. a KDE Plasma global shortcut running
``vocalinux --toggle``) can control a running instance without the internal
``voculinux --toggle``) can control a running instance without the internal
evdev/pynput hotkey listener reading ``/dev/input``.
Mirrors the GDBus usage already present in ``suspend_handler.py``.
@@ -37,7 +37,7 @@ _INTROSPECTION_XML = f"""
class VocalinuxDBusService:
"""Owns the Vocalinux session-bus name and dispatches control methods.
"""Owns the Voculinux session-bus name and dispatches control methods.
Method calls are marshalled onto the GLib/GTK main thread via
:func:`GLib.idle_add` before the callbacks run, so recognition start/stop
+1 -1
View File
@@ -1,4 +1,4 @@
"""Optional local VocaGateway lifecycle helpers for Vocalinux.
"""Optional local VocaGateway lifecycle helpers for Voculinux.
This is an opt-in path for power users who want to run
https://github.com/VocaHQ/vocagateway on the same Linux box via podman
+5 -5
View File
@@ -143,7 +143,7 @@ class GatewayEmbedManager:
return
threading.Thread(
target=_worker,
name="vocalinux-gateway-runtime",
name="voculinux-gateway-runtime",
daemon=True,
).start()
@@ -163,7 +163,7 @@ class GatewayEmbedManager:
self.lan_publish = lan_publish
thread = threading.Thread(
target=self._start_worker,
name="vocalinux-gateway-start",
name="voculinux-gateway-start",
daemon=True,
)
thread.start()
@@ -171,7 +171,7 @@ class GatewayEmbedManager:
def stop_async(self) -> None:
thread = threading.Thread(
target=self._stop_worker,
name="vocalinux-gateway-stop",
name="voculinux-gateway-stop",
daemon=True,
)
thread.start()
@@ -201,7 +201,7 @@ class GatewayEmbedManager:
self._republish_started = True
thread = threading.Thread(
target=self._republish_worker,
name="vocalinux-gateway-republish",
name="voculinux-gateway-republish",
daemon=True,
)
thread.start()
@@ -303,7 +303,7 @@ class GatewayEmbedManager:
logger.exception("gateway poll failed")
self._poll_thread = threading.Thread(
target=_loop, name="vocalinux-gateway-poll", daemon=True
target=_loop, name="voculinux-gateway-poll", daemon=True
)
self._poll_thread.start()
+1 -1
View File
@@ -27,7 +27,7 @@ def xdg_cache_home() -> str:
def gateway_cache_dir() -> str:
return os.path.join(xdg_cache_home(), "vocalinux", f"vocagateway-{GATEWAY_RELEASE_TAG}")
return os.path.join(xdg_cache_home(), "voculinux", f"vocagateway-{GATEWAY_RELEASE_TAG}")
def gateway_embed_config_dir() -> str:
+1 -1
View File
@@ -1,4 +1,4 @@
"""Map a paired local gateway onto Vocalinux remote_api settings."""
"""Map a paired local gateway onto Voculinux remote_api settings."""
from __future__ import annotations
+1 -1
View File
@@ -132,7 +132,7 @@ def detect_container_runtime(
binary=None,
hint=(
"Install podman (preferred) or docker to run a local VocaGateway. "
"Vocalinux does not bundle a container engine in the AppImage."
"Voculinux does not bundle a container engine in the AppImage."
),
)
+2 -2
View File
@@ -34,8 +34,8 @@ def detect_sandbox(
blocked=True,
kind="flatpak",
hint=(
"Vocalinux is running as a Flatpak and cannot reach the host "
"podman/docker socket. Run Vocalinux from the native package or "
"Voculinux is running as a Flatpak and cannot reach the host "
"podman/docker socket. Run Voculinux from the native package or "
"AppImage, or start VocaGateway on the host and point Remote "
"Server at it. See docs/GATEWAY_EMBED.md."
),
+120
View File
@@ -0,0 +1,120 @@
"""One-time legacy Vocalinux import for the Voculinux fork.
Ticket #18: on first run, a previous Vocalinux install's user data is copied
once into the new ``voculinux`` locations — ``config.json``, the custom
dictionary (``dictionary.txt`` plus ``custom-dictionary-corrections.json``),
and the RemoteDesktop portal restore token. After that the new configuration
always wins: no merging, no write-back to the legacy location.
Exactly-once falls out of existence checks — a copied file is never copied
again, and a run that already has new locations imports nothing.
"""
import logging
import os
import shutil
from .app_identity import APP_DIR_NAME, LEGACY_APP_DIR_NAME
logger = logging.getLogger(__name__)
#: Directory basenames, deliberately literal: the legacy name must keep
#: resolving to the old install even after the rename.
LEGACY_CONFIG_DIRNAME: str = LEGACY_APP_DIR_NAME
NEW_CONFIG_DIRNAME: str = APP_DIR_NAME
CONFIG_FILENAME: str = "config.json"
TERMS_FILENAME: str = "dictionary.txt"
CORRECTIONS_FILENAME: str = "custom-dictionary-corrections.json"
RESTORE_TOKEN_FILENAME: str = "remote_desktop_restore_token"
#: ``(subdirectory, filename)`` pairs copied ``config -> config`` or
#: ``data -> data``. Models are deliberately absent: they re-resolve through
#: the pinned-checksum download path instead of being migrated.
CONFIG_PAYLOAD: tuple[tuple[str, str], ...] = (
("", CONFIG_FILENAME),
("", TERMS_FILENAME),
("", CORRECTIONS_FILENAME),
)
DATA_PAYLOAD: tuple[tuple[str, str], ...] = (("", RESTORE_TOKEN_FILENAME),)
def _xdg_dir(env_var: str, fallback: str) -> str:
"""Return an XDG base directory (empty values treated as unset)."""
return os.environ.get(env_var) or os.path.expanduser(fallback)
def legacy_config_dir() -> str:
"""Return the previous Vocalinux configuration directory."""
return os.path.join(_xdg_dir("XDG_CONFIG_HOME", "~/.config"), LEGACY_CONFIG_DIRNAME)
def new_config_dir() -> str:
"""Return the Voculinux configuration directory."""
return os.path.join(_xdg_dir("XDG_CONFIG_HOME", "~/.config"), NEW_CONFIG_DIRNAME)
def legacy_data_dir() -> str:
"""Return the previous Vocalinux data directory."""
return os.path.join(_xdg_dir("XDG_DATA_HOME", "~/.local/share"), LEGACY_CONFIG_DIRNAME)
def new_data_dir() -> str:
"""Return the Voculinux data directory."""
return os.path.join(_xdg_dir("XDG_DATA_HOME", "~/.local/share"), NEW_CONFIG_DIRNAME)
def _copy_once(src: str, dst: str) -> bool:
"""Copy ``src`` to ``dst`` when the destination does not exist yet.
Returns True when a copy happened. Missing sources, existing
destinations, and I/O errors copy nothing and return False.
"""
if os.path.exists(dst):
return False
if not os.path.isfile(src):
return False
try:
os.makedirs(os.path.dirname(dst) or ".", exist_ok=True)
shutil.copy2(src, dst)
except OSError as error:
logger.warning("Legacy import skipped %s: %s", src, error)
return False
logger.info("Imported legacy file %s to %s", src, dst)
return True
def import_legacy_once() -> list[str]:
"""Copy legacy Vocalinux user data into the new locations, once.
Preconditions (all must hold, otherwise nothing is imported):
- the new configuration directory has no ``config.json`` yet (an
existing new configuration always wins, with no merge and no
write-back to the legacy location);
- at least one legacy payload file exists.
Returns the list of destination paths that were created.
"""
new_config = os.path.join(new_config_dir(), CONFIG_FILENAME)
if os.path.exists(new_config):
return []
legacy_config = legacy_config_dir()
if not os.path.isdir(legacy_config):
return []
imported: list[str] = []
for _subdir, filename in CONFIG_PAYLOAD:
src = os.path.join(legacy_config, filename)
dst = os.path.join(new_config_dir(), filename)
if _copy_once(src, dst):
imported.append(dst)
legacy_data = legacy_data_dir()
if os.path.isdir(legacy_data):
for _subdir, filename in DATA_PAYLOAD:
src = os.path.join(legacy_data, filename)
dst = os.path.join(new_data_dir(), filename)
if _copy_once(src, dst):
imported.append(dst)
if imported:
logger.info("Legacy Vocalinux import complete: %d file(s)", len(imported))
return imported
+21 -11
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
Main entry point for Vocalinux application.
Main entry point for Voculinux application.
"""
import argparse
@@ -157,11 +157,11 @@ def _dispatch_trigger(command: str) -> int:
from .dbus_service import send_command
if send_command(command):
logger.info("Sent '%s' command to running Vocalinux instance", command)
logger.info("Sent '%s' command to running Voculinux instance", command)
return 0
logger.error(
"Could not reach a running Vocalinux instance to '%s'. Is Vocalinux running?",
"Could not reach a running Voculinux instance to '%s'. Is Voculinux running?",
command,
)
return 1
@@ -257,7 +257,7 @@ def check_dependencies():
)
logger.error("")
logger.error(
"For pipx users: Install system packages BEFORE running 'pipx install vocalinux'"
"For pipx users: Install system packages BEFORE running 'pipx install voculinux'"
)
logger.error("")
logger.error("For the best experience, use the recommended installer:")
@@ -304,12 +304,12 @@ def check_display_available():
display = Gdk.Display.get_default()
if display is None:
logger.error("No display available. Vocalinux requires a graphical environment.")
logger.error("No display available. Voculinux requires a graphical environment.")
logger.error("")
logger.error("If running remotely, ensure DISPLAY is set:")
logger.error(" export DISPLAY=:0")
logger.error("")
logger.error("If running in a headless environment, Vocalinux cannot run.")
logger.error("If running in a headless environment, Voculinux cannot run.")
return False
return True
except Exception as e:
@@ -394,9 +394,19 @@ def main():
# Register cleanup to release lock on exit
atexit.register(single_instance.release_lock)
# One-time legacy Voculinux import (ticket #18): copies a previous
# install's config, dictionary, and portal token into the new locations
# before anything reads them. No-op on every later run.
try:
from .legacy_import import import_legacy_once
import_legacy_once()
except Exception as error:
logger.debug("Legacy import skipped: %s", error)
# Check dependencies first (before importing GTK-dependent modules)
if not check_dependencies():
logger.error("Cannot start Vocalinux due to missing dependencies")
logger.error("Cannot start Voculinux due to missing dependencies")
sys.exit(1)
# Identity for WM class / AppIndicator title (otherwise shows as main.py)
@@ -574,7 +584,7 @@ def main():
)
# Initialize main components
logger.info("Initializing Vocalinux...")
logger.info("Initializing Voculinux...")
try:
# Initialize speech recognition engine with saved/configured settings
@@ -940,7 +950,7 @@ def main():
# script path mid-queue can never let a later segment overtake an
# earlier one still waiting behind a running script.
post_processing_executor = ThreadPoolExecutor(
max_workers=1, thread_name_prefix="vocalinux-post-processing"
max_workers=1, thread_name_prefix="voculinux-post-processing"
)
# Cleared by the quit hook so a queued or in-flight job drops its
# segment instead of injecting while the injector is being stopped.
@@ -996,7 +1006,7 @@ def main():
logger.exception("Focus probe crashed unexpectedly: %s", exc)
probe_result.put(None)
threading.Thread(target=probe, daemon=True, name="vocalinux-focus-probe").start()
threading.Thread(target=probe, daemon=True, name="voculinux-focus-probe").start()
return probe_result
def _probe_result(
@@ -1374,7 +1384,7 @@ def main():
indicator.run()
except Exception as e:
logger.error(f"Failed to initialize Vocalinux: {e}")
logger.error(f"Failed to initialize Voculinux: {e}")
logger.error("Please check the logs above for more details")
sys.exit(1)
+1 -1
View File
@@ -1 +1 @@
"""Bundled resources (icons, sounds) for Vocalinux."""
"""Bundled resources (icons, sounds) for Voculinux."""
@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 1024 1024" role="img" aria-label="Vocalinux idle">
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 1024 1024" role="img" aria-label="Voculinux idle">
<!-- Opaque disc for StatusNotifier trays (~22px) -->
<circle cx="512" cy="512" r="480" fill="#0B1A15"/>
<g transform="translate(28.386 32.619) scale(2.116474)">

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 1024 1024" role="img" aria-label="Vocalinux processing">
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 1024 1024" role="img" aria-label="Voculinux processing">
<!-- Opaque disc for StatusNotifier trays (~22px) -->
<circle cx="512" cy="512" r="480" fill="#A86108"/>
<g transform="translate(28.386 32.619) scale(2.116474)">

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 1024 1024" role="img" aria-label="Vocalinux recording">
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 1024 1024" role="img" aria-label="Voculinux recording">
<!-- Opaque disc for StatusNotifier trays (~22px) -->
<circle cx="512" cy="512" r="480" fill="#C73D38"/>
<g transform="translate(28.386 32.619) scale(2.116474)">

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 460 460" width="460" height="460" role="img" aria-label="Vocalinux">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 460 460" width="460" height="460" role="img" aria-label="Voculinux">
<circle cx="229" cy="231.5" r="221.5" fill="#0F6B57"/>
<path d="M 164 127 A 65 65 0 0 1 294 127 L 294 224 A 65 65 0 0 1 164 224 Z" fill="#F2F6F2"/>
<path d="M 144.87 202.02 A 87 87 0 1 0 312.13 202.02" fill="none" stroke="#F2F6F2" stroke-width="18" stroke-linecap="round"/>

Before

Width:  |  Height:  |  Size: 887 B

After

Width:  |  Height:  |  Size: 887 B

+4 -4
View File
@@ -1,5 +1,5 @@
"""
Single instance enforcement for Vocalinux.
Single instance enforcement for Voculinux.
Uses file locking to ensure only one instance runs at a time.
Works across sessions and handles stale locks from crashed processes.
@@ -16,7 +16,7 @@ from .utils.paths import data_dir
logger = logging.getLogger(__name__)
# Lock file location: $XDG_DATA_HOME/vocalinux/ (defaults to ~/.local/share/vocalinux/)
# Lock file location: $XDG_DATA_HOME/voculinux/ (defaults to ~/.local/share/voculinux/)
LOCK_FILE_DIR = Path(data_dir())
LOCK_FILE_PATH = LOCK_FILE_DIR / "instance.lock"
@@ -61,8 +61,8 @@ def acquire_lock() -> bool:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except (OSError, IOError):
# Lock is held by another process
logger.warning("Another instance of Vocalinux is already running. Exiting.")
print("Another instance of Vocalinux is already running.", file=sys.stderr)
logger.warning("Another instance of Voculinux is already running. Exiting.")
print("Another instance of Voculinux is already running.", file=sys.stderr)
print(
f"If you're sure no other instance is running, delete: {LOCK_FILE_PATH}",
file=sys.stderr,
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Speech recognition module for Vocalinux
Speech recognition module for Voculinux
"""
from . import command_processor, recognition_manager # noqa: F401
@@ -1,5 +1,5 @@
"""
Command processor for Vocalinux.
Command processor for Voculinux.
This module processes text commands from speech recognition, such as
"new line", "period", etc.
@@ -1,4 +1,4 @@
"""Speech recognition engine registry for Vocalinux.
"""Speech recognition engine registry for Voculinux.
New engine implementations are registered here so the recognition manager can
discover them without hard-coding every backend.
@@ -1,4 +1,4 @@
"""Faster-Whisper speech recognition engine for Vocalinux.
"""Faster-Whisper speech recognition engine for Voculinux.
This module wraps `faster_whisper.WhisperModel` behind the Engine Protocol. It
reuses the same audio preprocessing used by the OpenAI Whisper backend and is
@@ -123,7 +123,7 @@ class FasterWhisperEngine:
self._model_initialized = False
def _normalize_language(self, language: Optional[str] = None) -> Optional[str]:
"""Map Vocalinux language codes to faster-whisper language codes."""
"""Map Voculinux language codes to faster-whisper language codes."""
language = self.language if language is None else language
if language == "auto":
return None
@@ -1,5 +1,5 @@
"""
Speech recognition manager module for Vocalinux.
Speech recognition manager module for Voculinux.
This module provides a unified interface to different speech recognition engines,
currently supporting VOSK, Whisper, and whisper.cpp.
@@ -179,7 +179,7 @@ def _pywhispercpp_distribution_version() -> Optional[tuple[int, ...]]:
AUR ``python-pywhispercpp-{cpu,cuda,rocm}`` are still 1.4.x. Those wheels
advertise ``context_params`` on ``Model.__init__`` then setattr it onto
``whisper_full_params``, which raises AttributeError (AUR comments on
vocalinux, GitHub #625).
voculinux, GitHub #625).
"""
try:
from importlib.metadata import version as pkg_version
@@ -585,7 +585,7 @@ class SpeechRecognitionManager:
if self._silero_vad is not None:
logger.info("Using Silero neural VAD")
else:
logger.info("Using amplitude-based VAD (install vocalinux[vad] for neural VAD)")
logger.info("Using amplitude-based VAD (install voculinux[vad] for neural VAD)")
# Audio device selection (None means use system default)
self.audio_device_index = kwargs.get("audio_device_index", None)
@@ -951,7 +951,7 @@ class SpeechRecognitionManager:
play_error_sound()
_show_notification(
"Language Shortcut Unavailable",
f"Vocalinux cannot dictate in {language} with the loaded model. "
f"Voculinux cannot dictate in {language} with the loaded model. "
"Choose a multilingual model in Settings to use this shortcut.",
"dialog-warning",
)
@@ -1078,7 +1078,7 @@ class SpeechRecognitionManager:
# Check if this is a pre-installed model
if any(self.vosk_model_path.startswith(sys_dir) for sys_dir in SYSTEM_MODELS_DIRS):
logger.info(f"Using pre-installed VOSK model from {self.vosk_model_path}")
elif os.path.exists(os.path.join(self.vosk_model_path, ".vocalinux_preinstalled")):
elif os.path.exists(os.path.join(self.vosk_model_path, ".voculinux_preinstalled")):
logger.info(f"Using installer-provided VOSK model from {self.vosk_model_path}")
else:
logger.info(f"Using existing VOSK model from {self.vosk_model_path}")
@@ -1136,7 +1136,7 @@ class SpeechRecognitionManager:
available here is comparing the model's on-disk footprint against
currently available memory before calling into the native loader
(issue #676: this spike killed the process via the kernel OOM
killer on a 16GB machine, with no exception Vocalinux could catch).
killer on a 16GB machine, with no exception Voculinux could catch).
"""
import psutil
@@ -1203,7 +1203,7 @@ class SpeechRecognitionManager:
self.model = None
# load_model() verifies the checkpoint against the sha256 in its URL,
# for a file already on disk as well as a fresh download, so Vocalinux
# for a file already on disk as well as a fresh download, so Voculinux
# never hashes Whisper checkpoints itself.
self.model = whisper.load_model(
self.model_size, device=device, download_root=whisper_cache_dir
@@ -1858,7 +1858,7 @@ class SpeechRecognitionManager:
else:
logger.warning(
"pywhispercpp is Vulkan-backed but vulkaninfo found no devices. "
"Install vulkan-tools so Vocalinux can pick a discrete GPU. "
"Install vulkan-tools so Voculinux can pick a discrete GPU. "
"whisper.cpp will default to GPU 0, which may be an iGPU on hybrid laptops."
)
elif host_backend != ComputeBackend.CPU:
@@ -1986,7 +1986,7 @@ class SpeechRecognitionManager:
logger.warning(f"Vulkan GPU initialization failed: {error}. Falling back to CPU backend.")
_show_notification(
"Vocalinux: GPU Fallback",
"Voculinux: GPU Fallback",
"Your GPU doesn't support whisper.cpp Vulkan.\n" "Switched to CPU mode - still fast!",
"dialog-information",
)
@@ -2573,7 +2573,7 @@ class SpeechRecognitionManager:
"""Extract transcript text from OpenAI-compatible ASR responses.
FunASR/SenseVoice wrappers may include extra metadata or segment arrays.
Vocalinux only injects the transcript text and ignores the metadata.
Voculinux only injects the transcript text and ignores the metadata.
"""
if payload is None:
return ""
@@ -2686,7 +2686,7 @@ class SpeechRecognitionManager:
url,
stream=True,
timeout=self._MODEL_DOWNLOAD_TIMEOUT,
headers={"User-Agent": f"vocalinux/{__version__}"},
headers={"User-Agent": f"voculinux/{__version__}"},
proxies=requests_proxies(),
)
except requests.exceptions.RequestException as e:
@@ -3441,7 +3441,7 @@ class SpeechRecognitionManager:
play_error_sound()
_show_notification(
"Dictation Paused",
"Vocalinux is paused because a listed game or app is running. "
"Voculinux is paused because a listed game or app is running. "
"Close that app or remove it from Auto-Pause settings to resume.",
"dialog-information",
)
@@ -3457,7 +3457,7 @@ class SpeechRecognitionManager:
play_error_sound()
_show_notification(
"Model Reload Failed",
"Vocalinux could not reload the speech model. "
"Voculinux could not reload the speech model. "
"Open Settings to check your engine and try again.",
"dialog-warning",
)
@@ -1,5 +1,5 @@
"""
Silero VAD wrapper for Vocalinux.
Silero VAD wrapper for Voculinux.
Provides neural-network-based Voice Activity Detection using the Silero VAD
ONNX model. Falls back gracefully when onnxruntime is unavailable.
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Suspend/resume handler for Vocalinux.
Suspend/resume handler for Voculinux.
Monitors systemd-logind via D-Bus for PrepareForSleep signals and
automatically reinitializes the audio pipeline and speech model
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Text injection module for Vocalinux
Text injection module for Voculinux
"""
from . import text_injector # noqa: F401
@@ -330,9 +330,9 @@ def focused_xwayland_client_without_ibus() -> bool:
An X11 client only talks to IBus through an input method module that it
loads at startup from GTK_IM_MODULE (GTK), QT_IM_MODULE (Qt) or XMODIFIERS
(XIM). Its toolkit is read from the libraries it has mapped. Without
one, IBus has no input context for it: the Vocalinux engine still commits
one, IBus has no input context for it: the Voculinux engine still commits
and reports success, and the text goes nowhere (#972). The client's own
environment is read because it can differ from the one Vocalinux runs in.
environment is read because it can differ from the one Voculinux runs in.
While a native Wayland client holds focus, XWayland's active window is
unset or a placeholder with no pid and no WM_CLASS, so this returns False.
+52 -52
View File
@@ -1,8 +1,8 @@
"""
IBus engine for Vocalinux text injection.
IBus engine for Voculinux text injection.
This module provides an IBus input method engine that acts as a transparent
proxy - it passes all keystrokes through normally while allowing Vocalinux
proxy - it passes all keystrokes through normally while allowing Voculinux
to inject text directly via commit_text().
The engine should be set as the user's default input method. It will:
@@ -28,7 +28,7 @@ from pathlib import Path
from typing import Any, Callable, Mapping, Optional
# Package import when loaded normally; absolute / inline fallbacks when this
# file is executed by path (Vocalinux start_engine_process / IBus component exec).
# file is executed by path (Voculinux start_engine_process / IBus component exec).
try:
from ..app_identity import (
APP_NAME,
@@ -57,14 +57,14 @@ except ImportError: # pragma: no cover - exercised via script-path subprocess
except ImportError:
# Last resort: same semantics as the vocalinux.utils originals, except
# that host_env cannot strip a bundle it can no longer import.
# Identity literals below keep in step with vocalinux/app_identity.py.
APP_NAME = "Vocalinux"
COMPONENT_NAME = "org.freedesktop.IBus.Vocalinux"
ENGINE_DESCRIPTION = "Vocalinux voice dictation (use as default input method)"
ENGINE_LONGNAME = "Vocalinux"
ENGINE_NAME = "vocalinux"
IBUS_DIR_NAME = "vocalinux-ibus"
IBUS_TEXTDOMAIN = "vocalinux"
# Identity literals below keep in step with voculinux/app_identity.py.
APP_NAME = "Voculinux"
COMPONENT_NAME = "org.freedesktop.IBus.Voculinux"
ENGINE_DESCRIPTION = "Voculinux voice dictation (use as default input method)"
ENGINE_LONGNAME = "Voculinux"
ENGINE_NAME = "voculinux"
IBUS_DIR_NAME = "voculinux-ibus"
IBUS_TEXTDOMAIN = "voculinux"
def host_env(base: Optional[Mapping[str, str]] = None) -> dict[str, str]:
return dict(os.environ if base is None else base)
@@ -107,9 +107,9 @@ except (ImportError, ValueError) as e:
# File paths for communication ($XDG_DATA_HOME, defaults to ~/.local/share).
# Identity strings come from vocalinux/app_identity.py (see the import above);
# the homepage below is the retained upstream URL, never renamed.
VOCALINUX_IBUS_DIR = Path(xdg_data_home()) / IBUS_DIR_NAME
SOCKET_PATH = VOCALINUX_IBUS_DIR / "inject.sock"
PID_FILE = VOCALINUX_IBUS_DIR / "engine.pid"
VOCULINUX_IBUS_DIR = Path(xdg_data_home()) / IBUS_DIR_NAME
SOCKET_PATH = VOCULINUX_IBUS_DIR / "inject.sock"
PID_FILE = VOCULINUX_IBUS_DIR / "engine.pid"
# Shared metadata used by component XML, --xml output, and runtime registration.
# Kept in one place to prevent drift between the three consumers.
@@ -132,9 +132,9 @@ _COMPONENT_META = {
def ensure_ibus_dir() -> None:
"""Ensure the IBus data directory exists with secure permissions."""
VOCALINUX_IBUS_DIR.mkdir(parents=True, exist_ok=True)
VOCULINUX_IBUS_DIR.mkdir(parents=True, exist_ok=True)
# Secure the directory - only owner can access (prevents socket hijacking)
VOCALINUX_IBUS_DIR.chmod(0o700)
VOCULINUX_IBUS_DIR.chmod(0o700)
def verify_peer_credentials(conn: socket.socket) -> bool:
@@ -365,7 +365,7 @@ def _get_exec_command() -> str:
def is_engine_active() -> bool:
"""Check if the Vocalinux IBus engine is currently active."""
"""Check if the Voculinux IBus engine is currently active."""
try:
import subprocess
@@ -494,7 +494,7 @@ def get_current_engine_gnome_fallback() -> Optional[str]:
Explicit IBus source IDs are returned directly. XKB source IDs are matched
against registered IBus engines; names such as ``xkb:cn::`` are never
fabricated because a failed restoration can leave Vocalinux active.
fabricated because a failed restoration can leave Voculinux active.
Returns:
A verified IBus engine ID, or None if the current source cannot be
@@ -811,7 +811,7 @@ def sync_xwayland_layout_from_gnome() -> bool:
On GNOME Wayland, Mutter hands XWayland one keymap holding every
configured layout as a group plus all ``xkb-options``; input-source
switches only move the active group inside it. The Vocalinux engine
switches only move the active group inside it. The Voculinux engine
round-trip (``ibus engine xkb:*`` applies a single-layout map) — and any
plain ``setxkbmap -layout X`` — leaves XWayland with a one-group map:
XKB group toggles like ``grp:alt_shift_toggle`` have no second group to
@@ -987,7 +987,7 @@ def switch_engine(engine_name: str) -> bool:
def is_engine_process_running() -> bool:
"""Check if the Vocalinux IBus engine process is running."""
"""Check if the Voculinux IBus engine process is running."""
try:
if not PID_FILE.exists():
return False
@@ -999,7 +999,7 @@ def is_engine_process_running() -> bool:
cmdline_path = Path(f"/proc/{pid}/cmdline")
if cmdline_path.exists():
cmdline = cmdline_path.read_text()
return "ibus_engine.py" in cmdline and "vocalinux" in cmdline
return "ibus_engine.py" in cmdline and "voculinux" in cmdline
return True # Process exists but can't verify cmdline
except (OSError, ValueError, FileNotFoundError):
# Process doesn't exist or PID file is invalid
@@ -1012,7 +1012,7 @@ def start_engine_process() -> bool:
"""
Start the IBus engine process in the background.
This should be called by Vocalinux on startup to ensure the engine
This should be called by Voculinux on startup to ensure the engine
is running before attempting to switch to it.
Returns:
@@ -1042,7 +1042,7 @@ def start_engine_process() -> bool:
# keeps early import/startup failures visible without SIGPIPE risk if the
# engine keeps running and continues to log after we return.
ensure_ibus_dir()
stderr_log = VOCALINUX_IBUS_DIR / "engine.stderr.log"
stderr_log = VOCULINUX_IBUS_DIR / "engine.stderr.log"
with open(stderr_log, "w", encoding="utf-8") as stderr_fh:
process = subprocess.Popen(
[sys.executable, str(engine_script)],
@@ -1121,7 +1121,7 @@ def stop_engine_process(wait_timeout: float = 2.0) -> None:
cmdline_path = Path(f"/proc/{pid}/cmdline")
if cmdline_path.exists():
cmdline = cmdline_path.read_text()
if "ibus_engine.py" not in cmdline or "vocalinux" not in cmdline:
if "ibus_engine.py" not in cmdline or "voculinux" not in cmdline:
logger.warning(f"PID {pid} is not our engine process, skipping kill")
PID_FILE.unlink()
return
@@ -1163,7 +1163,7 @@ IBusEngineBase: Any = IBus.Engine if IBUS_AVAILABLE else object
class VocalinuxEngine(IBusEngineBase):
"""
IBus proxy engine for Vocalinux text injection.
IBus proxy engine for Voculinux text injection.
This engine acts as a transparent proxy:
- All keyboard input passes through unchanged
@@ -1189,7 +1189,7 @@ class VocalinuxEngine(IBusEngineBase):
_FOCUS_WAIT_TIMEOUT_S: float = 1.0
def __init__(self):
"""Initialize the Vocalinux IBus engine."""
"""Initialize the Voculinux IBus engine."""
if IBUS_AVAILABLE:
super().__init__()
logger.debug("VocalinuxEngine instance created")
@@ -1382,7 +1382,7 @@ class VocalinuxEngine(IBusEngineBase):
class VocalinuxEngineApplication:
"""
Application wrapper for running the Vocalinux IBus engine.
Application wrapper for running the Voculinux IBus engine.
This is used when the engine is launched by IBus as a separate process.
"""
@@ -1392,7 +1392,7 @@ class VocalinuxEngineApplication:
Args:
exec_by_ibus: True when IBus launched this process (via --ibus flag).
False when Vocalinux launched it directly.
False when Voculinux launched it directly.
"""
if not IBUS_AVAILABLE:
raise RuntimeError("IBus is not available")
@@ -1423,7 +1423,7 @@ class VocalinuxEngineApplication:
logger.error("bus.request_name() failed")
raise RuntimeError("Failed to acquire IBus D-Bus name")
else:
# Launched by Vocalinux directly — register the full component
# Launched by Voculinux directly — register the full component
# so IBus discovers our engine without having launched us.
component = IBus.Component(
name=COMPONENT_NAME,
@@ -1444,10 +1444,10 @@ class VocalinuxEngineApplication:
raise RuntimeError("Failed to register IBus component")
logger.info("Registered component with IBus (standalone mode)")
logger.info("Vocalinux IBus engine started")
logger.info("Voculinux IBus engine started")
# Start the socket server immediately so it's ready for connections
# even before the engine is activated via `ibus engine vocalinux`
# even before the engine is activated via `ibus engine voculinux`
VocalinuxEngine._start_socket_server()
def _on_disconnected(self, bus: "IBus.Bus") -> None:
@@ -1465,10 +1465,10 @@ class IBusTextInjector:
"""
Text injector that uses IBus for text injection.
This class connects to the Vocalinux IBus engine via Unix socket
This class connects to the Voculinux IBus engine via Unix socket
and sends text to be injected. The normal TextInjector warmup path starts
the engine process without selecting it as the user's active IBus engine.
Each injection temporarily switches to the Vocalinux engine, commits the
Each injection temporarily switches to the Voculinux engine, commits the
text, and restores the user's previous engine.
The auto_activate=True path still performs the older eager activation flow
@@ -1499,14 +1499,14 @@ class IBusTextInjector:
"""
Start the IBus engine process without selecting it as the active engine.
The Vocalinux engine is only needed while committing dictated text. Keeping
The Voculinux engine is only needed while committing dictated text. Keeping
the user's normal IBus/XKB engine active preserves dead-key composition and
layout-specific behavior during ordinary typing.
Captures the current engine for shutdown restore only. Injection still
resolves the restore target at commit time so a stale warmup value is not
used mid-session (see #623); shutdown needs a fallback when quit leaves
Vocalinux selected after a failed inject restore (#558).
Voculinux selected after a failed inject restore (#558).
"""
if not is_engine_active():
captured = get_current_engine()
@@ -1538,28 +1538,28 @@ class IBusTextInjector:
f"variant: {self._previous_xkb_layout[1]}, option: {self._previous_xkb_layout[2]}"
)
# Save current engine and switch to Vocalinux
# Save current engine and switch to Voculinux
if not is_engine_active():
self._previous_engine = get_current_engine()
if self._previous_engine:
logger.info(f"Saving current engine: {self._previous_engine}")
logger.info("Activating Vocalinux IBus engine...")
logger.info("Activating Voculinux IBus engine...")
if switch_engine(ENGINE_NAME):
logger.info("Vocalinux IBus engine activated")
logger.info("Voculinux IBus engine activated")
else:
raise IBusSetupError(
"Failed to activate Vocalinux IBus engine. "
"Try manually: ibus engine vocalinux"
"Failed to activate Voculinux IBus engine. "
"Try manually: ibus engine voculinux"
)
self._wait_for_engine_ready()
# Restore the user's XKB layout immediately after engine activation.
# Switching to the Vocalinux IBus engine can override the system
# Switching to the Voculinux IBus engine can override the system
# keyboard layout (e.g. Spanish, French AZERTY) with the engine's
# default layout. Re-applying the captured XKB layout ensures the
# user's keyboard keeps working correctly while Vocalinux is active.
# user's keyboard keeps working correctly while Voculinux is active.
# See issue #292.
if self._previous_xkb_layout:
layout, variant, option = self._previous_xkb_layout
@@ -1598,15 +1598,15 @@ class IBusTextInjector:
delay = min(delay * 2, 2.0)
raise IBusSetupError(
"Vocalinux IBus engine did not become ready after startup. "
"Try manually: ibus engine vocalinux"
"Voculinux IBus engine did not become ready after startup. "
"Try manually: ibus engine voculinux"
)
def stop(self) -> None:
"""
Stop the IBus text injector and restore previous engine and XKB layout.
Call this when Vocalinux is shutting down. If no valid engine was
Call this when Voculinux is shutting down. If no valid engine was
captured before injection (e.g. on GNOME/Wayland with no global IBus
engine), restoration is skipped to avoid switching the user to a
wrong layout (see issue #497).
@@ -1616,12 +1616,12 @@ class IBusTextInjector:
``register_component`` makes IBus run ``check_global_engine()``, which
only searches ``register_engine_list`` and treats XML engines such as
``xkb:es::spa`` as missing, clearing the global engine even when
Vocalinux was never selected (issue #558).
Voculinux was never selected (issue #558).
Prefer the *live* engine at quit when it is a real non-Vocalinux
Prefer the *live* engine at quit when it is a real non-Voculinux
engine, so a user who changed input source after warmup is not
switched back to a stale cache. Fall back to ``_previous_engine`` only
when the live engine is Vocalinux or unavailable (failed inject restore).
when the live engine is Voculinux or unavailable (failed inject restore).
"""
current = get_current_engine()
restore_engine: Optional[str]
@@ -1698,7 +1698,7 @@ class IBusTextInjector:
logger.info(f"Starting IBus text injection: '{text[:20]}...' (length: {len(text)})")
restore_engine: Optional[str] = None
# Capture before any engine switch. Switching to Vocalinux can override
# Capture before any engine switch. Switching to Voculinux can override
# the system XKB map to us; scoped injection must restore it after
# switching back (#292, #664). Empty on Wayland (no-op restore).
captured_xkb = get_current_xkb_layout()
@@ -1716,10 +1716,10 @@ class IBusTextInjector:
# quit does not restore a stale warmup value (Bugbot on #643 / #558).
self._previous_engine = current_engine
logger.debug(
f"Temporarily activating Vocalinux IBus engine (will restore {current_engine})"
f"Temporarily activating Voculinux IBus engine (will restore {current_engine})"
)
if not switch_engine(ENGINE_NAME):
logger.error("Failed to activate Vocalinux IBus engine for injection")
logger.error("Failed to activate Voculinux IBus engine for injection")
return False
# Try injection with bounded retries for transient socket/engine races.
@@ -1752,7 +1752,7 @@ class IBusTextInjector:
logger.error(
"IBus engine socket not found. "
"Make sure Vocalinux IBus engine is running."
"Make sure Voculinux IBus engine is running."
)
return False
@@ -1,5 +1,5 @@
"""
RemoteDesktop portal text injection for Vocalinux.
RemoteDesktop portal text injection for Voculinux.
``org.freedesktop.portal.RemoteDesktop`` is the only key-injection path
Wayland itself sanctions: the compositor's portal backend prompts the user
@@ -325,7 +325,7 @@ class RemoteDesktopPortal:
self._ready.clear()
self._connect_error = None
self._worker = threading.Thread(
target=self._worker_main, name="vocalinux-portal", daemon=True
target=self._worker_main, name="voculinux-portal", daemon=True
)
self._worker.start()
if not self._ready.wait(10):
+16 -16
View File
@@ -1,5 +1,5 @@
"""
Text injection module for Vocalinux.
Text injection module for Voculinux.
This module is responsible for injecting recognized text into the active
application, supporting both X11 and Wayland environments.
@@ -104,7 +104,7 @@ def _kde_wayland_ibus_hint() -> str:
"""Return the KDE Plasma Wayland IBus setup hint."""
return (
"Open System Settings -> Keyboard -> Virtual Keyboard, select "
"'IBus Wayland', then restart Vocalinux or log out and back in."
"'IBus Wayland', then restart Voculinux or log out and back in."
)
@@ -245,7 +245,7 @@ class TextInjector:
"""
Clean up resources and restore previous state.
Call this when shutting down Vocalinux.
Call this when shutting down Voculinux.
"""
with self._state_lock:
if self._ibus_injector:
@@ -318,7 +318,7 @@ class TextInjector:
"weston",
)
# Backends a user may pin explicitly, via VOCALINUX_FORCE_BACKEND or the
# Backends a user may pin explicitly, via VOCULINUX_FORCE_BACKEND or the
# text_injection.backend setting. Named once so the two readers and the two
# "expected ..." messages cannot drift apart as values are added.
_SELECTABLE_BACKENDS = ("ibus", "portal", "wtype", "ydotool", "xdotool")
@@ -534,7 +534,7 @@ class TextInjector:
Host ydotool 0.1.x often works without a daemon; starting one is still
safe when ydotoold is installed. The CLI still needs a writable
/dev/uinput (the v0.18.x GitHub sideload Snap declares the plug:
``snap connect vocalinux:uinput``; the Store snap ships neither plug
``snap connect voculinux:uinput``; the Store snap ships neither plug
nor ydotool).
"""
if self._is_ydotoold_running():
@@ -576,7 +576,7 @@ class TextInjector:
@staticmethod
def _forced_backend_setting() -> Optional[str]:
"""``VOCALINUX_FORCE_BACKEND`` as three distinct states.
"""``VOCULINUX_FORCE_BACKEND`` as three distinct states.
``None`` when the variable is not set, ``"auto"`` when it is explicitly
set to auto, otherwise the backend name. The distinction matters:
@@ -599,7 +599,7 @@ class TextInjector:
if value in TextInjector._SELECTABLE_BACKENDS:
return value
logger.warning(
"Ignoring unknown VOCALINUX_FORCE_BACKEND=%r (expected %s)",
"Ignoring unknown VOCULINUX_FORCE_BACKEND=%r (expected %s)",
value,
TextInjector._accepted_backends_help(),
)
@@ -624,7 +624,7 @@ class TextInjector:
return (
"ydotool cannot open /dev/uinput. The Store snap ships no "
"uinput plug; only the v0.18.x GitHub sideload declares it: "
"sudo snap connect vocalinux:uinput"
"sudo snap connect voculinux:uinput"
)
return (
"ydotool cannot open /dev/uinput. "
@@ -645,7 +645,7 @@ class TextInjector:
@staticmethod
def _forced_backend() -> str:
"""Backend pinned via ``VOCALINUX_FORCE_BACKEND``, or ``"auto"``.
"""Backend pinned via ``VOCULINUX_FORCE_BACKEND``, or ``"auto"``.
Autodetection has to infer whether IBus commits actually reach the
focused app, and it cannot verify that: ``commit_text()`` reports
@@ -780,7 +780,7 @@ class TextInjector:
used -- and so the environment is parsed once per construction, which
also means a malformed value is reported once rather than per caller.
``VOCALINUX_FORCE_BACKEND`` wins so a backend can still be A/B-tested
``VOCULINUX_FORCE_BACKEND`` wins so a backend can still be A/B-tested
for one run without editing (or permanently changing) the user's config.
That includes an explicit ``auto``, which asks for autodetection this run
and so must short-circuit here rather than fall through to the saved pin.
@@ -918,7 +918,7 @@ class TextInjector:
)
)
# Bridging Wayland DEs (GNOME): inject_text() switches to the
# real vocalinux engine for each commit, so a bare xkb:* baseline is
# real voculinux engine for each commit, so a bare xkb:* baseline is
# fine (#501, #504). KDE joins that set only when KWin's Virtual
# Keyboard is confirmed on: a confirmed VK bridge is what makes the
# commits reach apps, and it is also what keeps the #752
@@ -934,7 +934,7 @@ class TextInjector:
# Check if IBus is the active input method (not just installed)
# This is important because IBus may be installed but not being used,
# e.g., when the user has configured ydotool or Fcitx instead.
# VOCALINUX_FORCE_BACKEND=ibus bypasses the reachability guards below
# VOCULINUX_FORCE_BACKEND=ibus bypasses the reachability guards below
# and goes straight to setup.
force_ibus = forced == "ibus"
if not force_ibus and not ibus_active and not wayland_scoped_ibus:
@@ -1278,7 +1278,7 @@ class TextInjector:
return
try:
# Keep the Vocalinux IBus process warm without selecting it as the
# Keep the Voculinux IBus process warm without selecting it as the
# user's active keyboard engine. Activation is scoped to each text
# commit so normal typing keeps layout-specific compose/dead keys.
self._ibus_injector.prepare_engine()
@@ -2778,7 +2778,7 @@ class TextInjector:
def _injection_modifier_wait_seconds(self) -> float:
"""Return the sanitized max seconds to wait for modifiers to release.
Reads VOCALINUX_INJECT_MODIFIER_WAIT and falls back to the default for
Reads VOCULINUX_INJECT_MODIFIER_WAIT and falls back to the default for
anything unusable: a non-numeric value, or a non-finite one. In
particular ``inf`` parses fine and is positive, so without this guard it
would make the wait deadline infinite and block injection forever while a
@@ -2793,7 +2793,7 @@ class TextInjector:
return self._DEFAULT_INJECT_MODIFIER_WAIT
if not math.isfinite(value):
logger.warning(
"VOCALINUX_INJECT_MODIFIER_WAIT=%r is not a finite number; " "using default %.1fs",
"VOCULINUX_INJECT_MODIFIER_WAIT=%r is not a finite number; " "using default %.1fs",
raw,
self._DEFAULT_INJECT_MODIFIER_WAIT,
)
@@ -2805,7 +2805,7 @@ class TextInjector:
Returns immediately if no modifier is held (the common case), so this
adds no latency unless the user is still holding their toggle/PTT
shortcut. Bounded by VOCALINUX_INJECT_MODIFIER_WAIT seconds (default 1.0;
shortcut. Bounded by VOCULINUX_INJECT_MODIFIER_WAIT seconds (default 1.0;
set to 0 to disable). Best-effort: if evdev/permissions are unavailable
it simply proceeds.
"""
+1 -1
View File
@@ -1,5 +1,5 @@
"""
User interface module for Vocalinux
User interface module for Voculinux
Note: GTK-dependent modules (tray_indicator) are imported lazily to allow
the package to be imported even when GTK/AppIndicator3 typelibs are not available.
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Action handler for Vocalinux voice commands.
Action handler for Voculinux voice commands.
This module handles special voice commands like "delete that", "undo", etc.
"""
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Audio feedback module for Vocalinux.
Audio feedback module for Voculinux.
This module provides audio feedback for various recognition states.
"""
+2 -2
View File
@@ -1,8 +1,8 @@
"""
Autostart manager for Vocalinux.
Autostart manager for Voculinux.
This module handles the creation and removal of the XDG autostart desktop entry
for automatically starting Vocalinux on login.
for automatically starting Voculinux on login.
"""
import logging
+4 -4
View File
@@ -1,5 +1,5 @@
"""
Configuration manager for Vocalinux.
Configuration manager for Voculinux.
This module handles loading, saving, and accessing user preferences.
"""
@@ -152,7 +152,7 @@ DEFAULT_CONFIG = {
"mode": "push_to_talk", # "toggle" or "push_to_talk"
# When True, the internal evdev/pynput listener is not started; activation
# comes in over D-Bus instead (e.g. a KDE Plasma global shortcut running
# `vocalinux --toggle`). Avoids /dev/input access and the input group.
# `voculinux --toggle`). Avoids /dev/input access and the input group.
"disable_internal_hotkey": False,
# Opt-in: forward shortcut key events while still detecting gestures.
"pass_shortcut_to_apps": False,
@@ -201,7 +201,7 @@ DEFAULT_CONFIG = {
"paste_long_text": False, # Faster clipboard paste for long X11 dictations
# Text-injection backend: "auto" autodetects, or pin "ibus"/"wtype"/
# "ydotool"/"xdotool" when autodetection is wrong (#476).
# VOCALINUX_FORCE_BACKEND overrides this for a single run.
# VOCULINUX_FORCE_BACKEND overrides this for a single run.
"backend": "auto",
# Route dictation into the in-app Dictation Pad window instead of
# injecting into other apps — the Wayland-safe fallback (#726).
@@ -210,7 +210,7 @@ DEFAULT_CONFIG = {
"history": {
"enabled": True, # Keep recent dictation snippets in the tray menu
"max_items": 10, # How many snippets to retain
# Also save snippets to $XDG_DATA_HOME/vocalinux/history.jsonl so they
# Also save snippets to $XDG_DATA_HOME/voculinux/history.jsonl so they
# survive restarts. Off by default: dictated text must not silently
# accumulate on disk unless the user asks for it (#758).
"persist": False,
+2 -2
View File
@@ -1,5 +1,5 @@
"""
Floating on-screen dictation overlay for Vocalinux.
Floating on-screen dictation overlay for Voculinux.
Shows a compact glowing indicator while recognition is active so users get a
clear visual cue beyond the system tray icon (similar to SuperWhisper-style
@@ -286,7 +286,7 @@ class DictationOverlay:
Settings unmaps it instead.
The window stays, but the request is passed to ``on_close_request``
so "Quit" from the dock quits Vocalinux like the tray's Quit (#1028).
so "Quit" from the dock quits Voculinux like the tray's Quit (#1028).
"""
if self._on_close_request is None:
logger.info(
+2 -2
View File
@@ -1,5 +1,5 @@
"""
In-app dictation pad for Vocalinux.
In-app dictation pad for Voculinux.
A small persistent window with an editable text view that receives dictated
text directly, bypassing cross-application text injection entirely. It is the
@@ -252,7 +252,7 @@ class DictationPad:
self._Gtk = Gtk
window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL)
window.set_title("Vocalinux Dictation Pad")
window.set_title("Voculinux Dictation Pad")
window.set_default_size(_PAD_WIDTH, _PAD_HEIGHT)
# Closing only hides: the pad keeps its buffer for the whole session
# and must not take the app down with it.
+5 -5
View File
@@ -1,5 +1,5 @@
"""
First-run dialog for Vocalinux.
First-run dialog for Voculinux.
This module shows a dialog on first run to ask the user about autostart preferences.
"""
@@ -25,7 +25,7 @@ class FirstRunDialog(Gtk.Dialog):
def __init__(self, parent: Optional[Gtk.Window] = None):
super().__init__(
title="Welcome to Vocalinux",
title="Welcome to Voculinux",
transient_for=parent,
flags=Gtk.DialogFlags.MODAL,
)
@@ -52,12 +52,12 @@ class FirstRunDialog(Gtk.Dialog):
box.set_margin_top(24)
box.set_margin_bottom(20)
title_label = Gtk.Label(label="<b>Welcome to Vocalinux!</b>", use_markup=True)
title_label = Gtk.Label(label="<b>Welcome to Voculinux!</b>", use_markup=True)
title_label.set_justify(Gtk.Justification.CENTER)
box.pack_start(title_label, False, False, 0)
description_label = Gtk.Label(
label="Vocalinux runs from your system tray so you can start and stop voice typing anytime.",
label="Voculinux runs from your system tray so you can start and stop voice typing anytime.",
wrap=True,
justify=Gtk.Justification.CENTER,
)
@@ -77,7 +77,7 @@ class FirstRunDialog(Gtk.Dialog):
box.pack_start(quick_guide_label, False, False, 0)
subtitle_label = Gtk.Label(
label="Would you like Vocalinux to start automatically when you log in?",
label="Would you like Voculinux to start automatically when you log in?",
wrap=True,
justify=Gtk.Justification.CENTER,
)
@@ -1,5 +1,5 @@
"""
Keyboard backend system for Vocalinux.
Keyboard backend system for Voculinux.
This package provides a modular backend system for global keyboard shortcuts,
supporting multiple platforms and display servers (X11, Wayland).
@@ -95,9 +95,9 @@ else:
_FORWARDED_EVENT_TYPES = frozenset()
# Suffix on every uinput clone name. ``_open_keyboard_device`` uses it to
# skip clones (also ones created by other Vocalinux processes), so it must
# skip clones (also ones created by other Voculinux processes), so it must
# survive the kernel's uinput name-length truncation.
_CLONE_NAME_SUFFIX = " (vocalinux)"
_CLONE_NAME_SUFFIX = " (voculinux)"
# Name fragments remapper-created keyboards are known to use. Only a
# fallback for systems where sysfs is unreadable (snap confinement): the
@@ -1234,16 +1234,16 @@ class EvdevDeviceHub:
logger.warning(f"Cannot open {device_path}: {e}")
return False
# Only Vocalinux's own uinput clones are never monitored — reading
# Only Voculinux's own uinput clones are never monitored — reading
# them would loop the events we forwarded straight back in. Clones
# are matched by their registered device path; the " (vocalinux)"
# name suffix also covers clones of other Vocalinux processes whose
# are matched by their registered device path; the " (voculinux)"
# name suffix also covers clones of other Voculinux processes whose
# paths we never registered. Other virtual devices (key remappers,
# accessibility keyboards) ARE monitored: users can bind shortcuts
# to them.
device_name = str(getattr(device, "name", "") or "")
if device_path in self._clone_paths or device_name.endswith(_CLONE_NAME_SUFFIX):
logger.debug(f"Skipping Vocalinux clone device: {device_path} ({device_name})")
logger.debug(f"Skipping Voculinux clone device: {device_path} ({device_name})")
try:
device.close()
except (OSError, IOError, RuntimeError) as e:
@@ -1953,8 +1953,8 @@ class EvdevKeyboardBackend(KeyboardBackend):
return (
"Snap is missing input-device access. "
"Connect once, then restart:\n"
"sudo snap connect vocalinux:raw-input\n"
"sudo snap connect vocalinux:hardware-observe"
"sudo snap connect voculinux:raw-input\n"
"sudo snap connect voculinux:hardware-observe"
)
return (
"Add your user to the 'input' group and log out/in:\n"
@@ -1967,8 +1967,8 @@ class EvdevKeyboardBackend(KeyboardBackend):
if in_snap:
return (
"Snap is missing input-device access. Connect once, then restart:\n"
"sudo snap connect vocalinux:raw-input\n"
"sudo snap connect vocalinux:hardware-observe"
"sudo snap connect voculinux:raw-input\n"
"sudo snap connect voculinux:hardware-observe"
)
try:
@@ -2034,7 +2034,7 @@ class EvdevKeyboardBackend(KeyboardBackend):
- Pure-modifier gestures (``right_alt+right_alt``, ``ctrl+ctrl``):
every event of the configured modifier side(s) is consumed — the
modifier is reserved for Vocalinux while the listener runs.
modifier is reserved for Voculinux while the listener runs.
- Bare function-key shortcuts (``f11``): every event of that key.
- Modifier+key combos (``alt+r``): the main key is consumed only
while the combo owns it (press with all modifiers held, plus the
+4 -4
View File
@@ -1,5 +1,5 @@
"""
Keyboard shortcut manager for Vocalinux.
Keyboard shortcut manager for Voculinux.
This module provides global keyboard shortcut functionality to
start/stop speech recognition with configurable shortcuts.
@@ -101,9 +101,9 @@ class KeyboardShortcutManager:
logger.warning("Keyboard shortcuts need Snap input access")
logger.warning("=" * 60)
logger.warning("Global hotkeys read /dev/input (evdev). Connect once:")
logger.warning(" sudo snap connect vocalinux:raw-input")
logger.warning(" sudo snap connect vocalinux:hardware-observe")
logger.warning("Then restart Vocalinux. Until then, use the system tray.")
logger.warning(" sudo snap connect voculinux:raw-input")
logger.warning(" sudo snap connect voculinux:hardware-observe")
logger.warning("Then restart Voculinux. Until then, use the system tray.")
logger.warning("=" * 60)
return
+2 -2
View File
@@ -1,5 +1,5 @@
"""
Logging viewer dialog for VocaLinux.
Logging viewer dialog for Voculinux.
This module provides a GTK dialog for viewing, filtering, and exporting
application logs for debugging purposes.
@@ -722,7 +722,7 @@ class LoggingDialog(Gtk.Dialog):
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
# Create header for clipboard content
header = f"VocaLinux Logs - Copied at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
header = f"Voculinux Logs - Copied at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
header += "=" * 80 + "\n\n"
# Combine header and content
+4 -4
View File
@@ -1,5 +1,5 @@
"""
Logging manager for VocaLinux.
Logging manager for Voculinux.
This module provides centralized logging functionality with UI integration,
allowing users to view, filter, and export application logs for debugging.
@@ -46,7 +46,7 @@ class LogRecord:
class LoggingManager:
"""
Centralized logging manager for VocaLinux.
Centralized logging manager for Voculinux.
This class captures all application logs, stores them in memory,
and provides functionality for viewing, filtering, and exporting logs.
@@ -64,7 +64,7 @@ class LoggingManager:
self.log_callbacks: list[Callable[[LogRecord], None]] = []
self.lock = threading.Lock()
# Create logs directory ($XDG_DATA_HOME/vocalinux/logs; Flatpak-safe)
# Create logs directory ($XDG_DATA_HOME/voculinux/logs; Flatpak-safe)
self.logs_dir = Path(data_dir()) / "logs"
self.logs_dir.mkdir(parents=True, exist_ok=True)
@@ -178,7 +178,7 @@ class LoggingManager:
records = self.get_logs(level_filter, module_filter)
with open(filepath, "w", encoding="utf-8") as f:
f.write("VocaLinux Log Export\n")
f.write("Voculinux Log Export\n")
f.write(f"Generated: {datetime.now().isoformat()}\n")
f.write(f"Total Records: {len(records)}\n")
if level_filter:
+13 -13
View File
@@ -18,7 +18,7 @@ from ..version import __copyright__, __url__, __version__
logger = logging.getLogger(__name__)
VOCALINUX_SITE_URL = "https://vocalinux.com"
VOCULINUX_SITE_URL = "https://voculinux.com"
VOCAHQ_SITE_URL = "https://vocahq.com"
VOCAMAC_SITE_URL = "https://vocamac.com"
VOCAWIN_SITE_URL = "https://vocawin.com"
@@ -33,7 +33,7 @@ VOCAHQ_MAILTO_URL = "mailto:hello@vocahq.com"
# About page links that are not GitHub release URLs.
_ABOUT_OPEN_URLS = frozenset(
{
VOCALINUX_SITE_URL,
VOCULINUX_SITE_URL,
VOCAHQ_SITE_URL,
VOCAMAC_SITE_URL,
VOCAWIN_SITE_URL,
@@ -130,11 +130,11 @@ _VOCAHQ_FAMILY_LINKS = (
"Open vocahq.com",
),
(
VOCALINUX_SITE_URL,
"Vocalinux",
VOCULINUX_SITE_URL,
"Voculinux",
"Linux, available now",
("platform-linux",),
"Open vocalinux.com",
"Open voculinux.com",
),
(
VOCAMAC_SITE_URL,
@@ -306,7 +306,7 @@ class AboutSectionMixin(Gtk.Dialog):
dark = _about_surface_is_dark()
about_icon = None
logo_path = ResourceManager().get_icon_path("vocalinux")
logo_path = ResourceManager().get_icon_path("voculinux")
if os.path.exists(logo_path):
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(logo_path)
@@ -317,7 +317,7 @@ class AboutSectionMixin(Gtk.Dialog):
logger.warning("Failed to load About icon: %s", exc)
app_group = PreferencesGroup(
title="Vocalinux",
title="Voculinux",
description=(
"Available now on Linux (X11 and Wayland). After a model is downloaded, "
"speech is processed on this PC."
@@ -331,7 +331,7 @@ class AboutSectionMixin(Gtk.Dialog):
self.about_version_label.get_style_context().add_class("preference-row-subtitle")
version_row = PreferenceRow(
title="Version",
subtitle="Currently installed Vocalinux build",
subtitle="Currently installed Voculinux build",
widget=self.about_version_label,
keywords=("version", "build"),
)
@@ -340,8 +340,8 @@ class AboutSectionMixin(Gtk.Dialog):
link_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
link_box.pack_start(
self._about_open_button(
VOCALINUX_SITE_URL,
"Open vocalinux.com website",
VOCULINUX_SITE_URL,
"Open voculinux.com website",
label="Website",
width=110,
dark=dark,
@@ -353,7 +353,7 @@ class AboutSectionMixin(Gtk.Dialog):
link_box.pack_start(
self._about_open_button(
GITHUB_REPO_URL,
"Open the Vocalinux GitHub repository",
"Open the Voculinux GitHub repository",
label="Source code",
width=120,
dark=dark,
@@ -364,9 +364,9 @@ class AboutSectionMixin(Gtk.Dialog):
)
links_row = PreferenceRow(
title="Open source",
subtitle="Product site at vocalinux.com, source on GitHub.",
subtitle="Product site at voculinux.com, source on GitHub.",
widget=link_box,
keywords=("website", "vocalinux.com", "github", "source", "repo", "code", "agpl"),
keywords=("website", "voculinux.com", "github", "source", "repo", "code", "agpl"),
)
app_group.add_row(links_row)
+18 -18
View File
@@ -1,5 +1,5 @@
"""
Settings Dialog for Vocalinux.
Settings Dialog for Voculinux.
Allows users to configure speech recognition engine, model size,
and other relevant parameters.
@@ -156,10 +156,10 @@ from .settings_about import ( # noqa: F401
VOCAHQ_MAILTO_URL,
VOCAHQ_SITE_URL,
VOCAHQ_X_URL,
VOCALINUX_SITE_URL,
VOCAMAC_SITE_URL,
VOCAPHONE_SITE_URL,
VOCAWIN_SITE_URL,
VOCULINUX_SITE_URL,
AboutSectionMixin,
_about_ink_hex,
_about_surface_is_dark,
@@ -2515,7 +2515,7 @@ def _populate_gpu_device_choices(combo: Any, saved_device: Optional[int]) -> Non
class SettingsDialog(AboutSectionMixin):
"""Modern GTK Dialog for configuring Vocalinux settings."""
"""Modern GTK Dialog for configuring Voculinux settings."""
def __init__(
self,
@@ -3281,7 +3281,7 @@ class SettingsDialog(AboutSectionMixin):
self.autostart_switch = _add_switch_row(
group,
title="Start on Login",
subtitle="Automatically start Vocalinux when you log in",
subtitle="Automatically start Voculinux when you log in",
keywords=("autostart", "boot", "startup"),
)
@@ -3295,7 +3295,7 @@ class SettingsDialog(AboutSectionMixin):
self.missing_tray_warning_switch = Gtk.Switch()
missing_tray_warning_row = PreferenceRow(
title="Warn if tray support is not detected",
subtitle="Show a warning when Vocalinux cannot detect AppIndicator support",
subtitle="Show a warning when Voculinux cannot detect AppIndicator support",
widget=self.missing_tray_warning_switch,
keywords=("tray", "appindicator", "warning"),
)
@@ -3315,7 +3315,7 @@ class SettingsDialog(AboutSectionMixin):
title="Transcription History",
description="Recent dictation snippets are kept in memory and shown "
"in the tray menu. History clears on quit unless Keep on Disk is on, "
"which saves them to history.jsonl under the Vocalinux data directory.",
"which saves them to history.jsonl under the Voculinux data directory.",
)
self.history_enabled_switch = Gtk.Switch()
@@ -3425,7 +3425,7 @@ class SettingsDialog(AboutSectionMixin):
terms_path_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
self.dictionary_terms_path_entry = Gtk.Entry()
self.dictionary_terms_path_entry.set_hexpand(True)
self.dictionary_terms_path_entry.set_placeholder_text("~/.config/vocalinux/dictionary.txt")
self.dictionary_terms_path_entry.set_placeholder_text("~/.config/voculinux/dictionary.txt")
self.dictionary_terms_path_entry.set_tooltip_text(
"UTF-8 line file used for custom terms and the accessibility scanner"
)
@@ -5080,8 +5080,8 @@ class SettingsDialog(AboutSectionMixin):
vad_info_label = Gtk.Label(
label=(
"Neural VAD is more accurate at separating speech from background "
"noise. To enable it, install onnxruntime and restart Vocalinux:\n"
' pip install "vocalinux[vad]"'
"noise. To enable it, install onnxruntime and restart Voculinux:\n"
' pip install "voculinux[vad]"'
),
xalign=0,
wrap=True,
@@ -5106,12 +5106,12 @@ class SettingsDialog(AboutSectionMixin):
)
# External activation: drive start/stop from a desktop/compositor global
# shortcut (bound to `vocalinux --toggle`) instead of the built-in key
# shortcut (bound to `voculinux --toggle`) instead of the built-in key
# listener. Avoids reading /dev/input (no keylogging, no `input` group).
self.disable_internal_hotkey_switch = Gtk.Switch()
self.disable_internal_hotkey_switch.set_tooltip_text(
"Turn off the built-in key listener and trigger voice typing from a "
"desktop global shortcut bound to 'vocalinux --toggle'. Avoids reading "
"desktop global shortcut bound to 'voculinux --toggle'. Avoids reading "
"/dev/input (no keylogging, no 'input' group needed)."
)
external_row = PreferenceRow(
@@ -5287,7 +5287,7 @@ class SettingsDialog(AboutSectionMixin):
if disabled:
self.shortcut_info_label.set_text(
"External activation is on: the built-in key listener is off. "
"Bind a desktop global shortcut to 'vocalinux --toggle' to start/stop."
"Bind a desktop global shortcut to 'voculinux --toggle' to start/stop."
)
else:
# Restore the mode-appropriate hint.
@@ -6258,7 +6258,7 @@ class SettingsDialog(AboutSectionMixin):
_style_combo(self.proxy_mode_combo)
_combo_chrome(
self.proxy_mode_combo,
tooltip="How Vocalinux connects to model hosts and update checks",
tooltip="How Voculinux connects to model hosts and update checks",
)
self.proxy_mode_combo.append("off", "Off")
self.proxy_mode_combo.append("system", "System")
@@ -6611,7 +6611,7 @@ class SettingsDialog(AboutSectionMixin):
self.latest_release_row.set_title("Release notes")
self.latest_release_row.set_subtitle("Unavailable")
self.open_release_btn.set_sensitive(True)
self.open_release_btn.set_tooltip_text("Open the Vocalinux project page")
self.open_release_btn.set_tooltip_text("Open the Voculinux project page")
self.open_release_btn.get_style_context().remove_class("suggested-action")
self.release_notes_group.hide()
# Clear dialog pending state so search restore cannot revive the New
@@ -6972,7 +6972,7 @@ class SettingsDialog(AboutSectionMixin):
self.gateway_embed_group.add_row(
PreferenceRow(
title="Remote API preset",
subtitle="Points Vocalinux at the local gateway",
subtitle="Points Voculinux at the local gateway",
widget=self.gateway_use_btn,
)
)
@@ -9390,7 +9390,7 @@ class SettingsDialog(AboutSectionMixin):
threading.Thread(
target=cancel_test,
daemon=True,
name="vocalinux-test-dictation-cancel",
name="voculinux-test-dictation-cancel",
).start()
def _finish_test_after_user_cancel(self) -> bool:
@@ -9464,7 +9464,7 @@ class SettingsDialog(AboutSectionMixin):
threading.Thread(
target=stop_test,
daemon=True,
name="vocalinux-test-dictation-stop",
name="voculinux-test-dictation-stop",
).start()
self._test_idle_wait_ticks = 0
@@ -9526,7 +9526,7 @@ class SettingsDialog(AboutSectionMixin):
threading.Thread(
target=self._cancel_buffered_reload_then_restore,
daemon=True,
name="vocalinux-test-dictation-cancel",
name="voculinux-test-dictation-cancel",
).start()
return False
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Diarized transcript dialog for VocaLinux.
Diarized transcript dialog for Voculinux.
Shows the speaker-attributed transcript produced by the "Transcribe audio
file" flow (TinyDiarize tdrz output via diarization.py) with copy and
+2 -2
View File
@@ -1,4 +1,4 @@
"""Recent-transcription history for Vocalinux.
"""Recent-transcription history for Voculinux.
Keeps a bounded, newest-first list of recent dictation snippets so the user
can review and re-copy recent voice input from the tray menu.
@@ -11,7 +11,7 @@ in a file.
Users who want transcripts to survive restarts can opt in via
``history.persist`` in config.json. Each snippet is then stored as one JSON
object per line in a ``history.jsonl`` file under the Vocalinux data
object per line in a ``history.jsonl`` file under the Voculinux data
directory — timestamped, with the session's duration, language, and model
recorded when the pipeline reported them — and the file is rewritten on
every change so it always mirrors the in-memory list. Turning persistence
+8 -8
View File
@@ -1,5 +1,5 @@
"""
System tray indicator module for Vocalinux.
System tray indicator module for Voculinux.
This module provides a system tray indicator for controlling the speech
recognition process and displaying its status.
@@ -158,7 +158,7 @@ _FALLBACK_KEYBOARD_RESTART_SECONDS = 6
class TrayIndicator:
"""
System tray indicator for Vocalinux.
System tray indicator for Voculinux.
This class provides a system tray icon with a menu for controlling
the speech recognition process.
@@ -258,7 +258,7 @@ class TrayIndicator:
# 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
# so a stale ~/.local/share/icons copy of voculinux-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"]),
@@ -340,7 +340,7 @@ class TrayIndicator:
self._setup_keyboard_shortcuts()
# Register the session-bus service so external triggers (e.g. a KDE
# Plasma global shortcut running `vocalinux --toggle`) can control this
# Plasma global shortcut running `voculinux --toggle`) can control this
# running instance. Handlers marshal onto the GTK main thread.
self._dbus_service = VocalinuxDBusService(
on_toggle=self._toggle_recognition,
@@ -379,7 +379,7 @@ class TrayIndicator:
)
notifications.notify(
"External activation unavailable",
"The D-Bus service could not start, so Vocalinux is using the "
"The D-Bus service could not start, so Voculinux is using the "
"internal keyboard shortcut instead. Check Settings -> Shortcuts.",
"dialog-warning",
)
@@ -721,7 +721,7 @@ class TrayIndicator:
text="System tray icon may not appear",
)
dialog.format_secondary_text(
"Vocalinux could not detect AppIndicator support in your desktop environment.\n"
"Voculinux could not detect AppIndicator support in your desktop environment.\n"
"\n"
"If you are using GNOME Shell, install the AppIndicator extension:\n"
" sudo apt install gnome-shell-extension-appindicator\n"
@@ -861,7 +861,7 @@ class TrayIndicator:
"""Start recognition for an external (D-Bus) trigger.
Uses normal start semantics — not push-to-talk — so a single
`vocalinux --start` transcribes immediately/with silence detection
`voculinux --start` transcribes immediately/with silence detection
rather than deferring until a Stop.
"""
with self._ptt_lock:
@@ -1071,7 +1071,7 @@ class TrayIndicator:
title = "Speech model downloaded"
body = (
f"{recommendation.display_name} is installed. "
"Dictation will be available when Vocalinux resumes."
"Dictation will be available when Voculinux resumes."
)
else:
shortcut = self.config_manager.get_str(
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Utility modules for Vocalinux.
Utility modules for Voculinux.
"""
from .resource_manager import ResourceManager
@@ -1,4 +1,4 @@
"""Faster-Whisper model information for Vocalinux.
"""Faster-Whisper model information for Voculinux.
Metadata and checksum-gated download URLs for the optional faster-whisper
backend (CTranslate2). Bundles are stored under ``models_dir()/faster_whisper/``
+1 -1
View File
@@ -1,4 +1,4 @@
"""Environment for the host binaries Vocalinux shells out to.
"""Environment for the host binaries Voculinux shells out to.
An AppImage puts its own libraries first on ``LD_LIBRARY_PATH``, and every child
process inherits that. A host tool linked against a newer GLib than the bundle
+3 -3
View File
@@ -1,6 +1,6 @@
"""Integrity verification for downloaded speech models.
Vocalinux pulls 40MB-2GB model files from Hugging Face, Alphacephei and
Voculinux pulls 40MB-2GB model files from Hugging Face, Alphacephei and
openaipublic.azureedge.net, then feeds them to native code -- whisper.cpp maps
its ggml files through ``ctypes.cdll``. A tampered or truncated model is
therefore not merely a bad transcription, so every download is checked against a
@@ -16,7 +16,7 @@ pins nothing they could not change alongside it). Regenerate it with
OpenAI Whisper ``.pt`` checkpoints are absent from it: ``openai-whisper``
downloads and verifies those itself, against the sha256 it reads out of the URL
path, so Vocalinux neither fetches nor hashes them. ``install.sh`` does preinstall
path, so Voculinux neither fetches nor hashes them. ``install.sh`` does preinstall
the tiny checkpoint, and verifies it there with its own bash implementation.
Verification fails closed: a file with no pinned digest is an error, not a
@@ -44,7 +44,7 @@ _MANIFEST_NAME = "model_checksums.txt"
# came out of. A directory has no hash of its own and the zip is deleted after
# unpacking, so this file is the only evidence the tree was ever verified.
# install.sh reads it under the same name; keep the two in step.
# (Name itself lives in vocalinux/app_identity.py so the rename flips it once.)
# (Name itself lives in voculinux/app_identity.py so the rename flips it once.)
# Hash of a 2GB model read in one go would sit in RAM; stream it instead.
_CHUNK_SIZE = 1024 * 1024
+1 -1
View File
@@ -1,4 +1,4 @@
# Checksums for every speech model Vocalinux downloads.
# Checksums for every speech model Voculinux downloads.
#
# Generated by scripts/generate-model-checksums.py -- do not edit by hand.
# Regenerate with `just model-checksums` after adding a model to
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Parakeet model information for Vocalinux.
Parakeet model information for Voculinux.
This module provides model metadata and download URLs for the Parakeet
engine, which runs NVIDIA NeMo ASR models through sherpa-onnx.
+2 -2
View File
@@ -16,12 +16,12 @@ def xdg_data_home() -> str:
def config_dir() -> str:
"""Return the Vocalinux configuration directory."""
"""Return the Voculinux configuration directory."""
return os.path.join(xdg_config_home(), APP_DIR_NAME)
def data_dir() -> str:
"""Return the Vocalinux data directory."""
"""Return the Voculinux data directory."""
return os.path.join(xdg_data_home(), APP_DIR_NAME)
+1 -1
View File
@@ -1,4 +1,4 @@
"""Outbound-proxy configuration shared by Vocalinux network calls.
"""Outbound-proxy configuration shared by Voculinux network calls.
Model downloads and update checks read the ``proxy`` section of config.json
through this module so every outbound path honors the same setting:
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Centralized resource manager for Vocalinux.
Centralized resource manager for Voculinux.
This module provides a unified way to locate and access application resources
like icons, sounds, and other assets regardless of how the application is installed.
+1 -1
View File
@@ -17,6 +17,6 @@ SNAP_GPU_NOTE = (
def running_in_snap(environ: Optional[Mapping[str, str]] = None) -> bool:
"""Whether this process runs inside the Vocalinux snap."""
"""Whether this process runs inside the Voculinux snap."""
env = os.environ if environ is None else environ
return bool(env.get("SNAP_NAME") or env.get("SNAP"))
+4 -4
View File
@@ -1,4 +1,4 @@
"""Check GitHub Releases for newer Vocalinux versions."""
"""Check GitHub Releases for newer Voculinux versions."""
import logging
import re
@@ -140,16 +140,16 @@ def is_update_available(
# github.com/jatinkrmalik/vocalinux redirects here after the org transfer.
_VOCALINUX_GITHUB_OWNERS = frozenset({"VocaHQ", "jatinkrmalik"})
_VOCULINUX_GITHUB_OWNERS = frozenset({"VocaHQ", "jatinkrmalik"})
def _trusted_path_prefixes(repo_url: str = __url__) -> tuple[str, ...]:
"""Return GitHub path prefixes that belong to this project."""
owner, repo = _repo_parts(repo_url)
prefixes = [f"/{owner}/{repo}"]
known_owners = {name.casefold() for name in _VOCALINUX_GITHUB_OWNERS}
known_owners = {name.casefold() for name in _VOCULINUX_GITHUB_OWNERS}
if repo.casefold() == "vocalinux" and owner.casefold() in known_owners:
prefixes.extend(f"/{name}/vocalinux" for name in sorted(_VOCALINUX_GITHUB_OWNERS))
prefixes.extend(f"/{name}/vocalinux" for name in sorted(_VOCULINUX_GITHUB_OWNERS))
seen: set[str] = set()
unique: list[str] = []
for prefix in prefixes:
+2 -2
View File
@@ -1,4 +1,4 @@
"""Background GitHub release checks for Vocalinux.
"""Background GitHub release checks for Voculinux.
Schedules a delayed first check, then repeats on an interval. Networking runs on
a worker thread; the result callback is delivered on the GLib main loop.
@@ -132,7 +132,7 @@ class UpdateMonitor:
target=self._worker,
args=(channel, __version__, generation),
daemon=True,
name="vocalinux-update-check",
name="voculinux-update-check",
).start()
def _worker(self, channel: str, current: str, generation: int) -> None:
+1 -1
View File
@@ -44,7 +44,7 @@ def whisper_model_file(model_size: str) -> str:
def migrate_legacy_checkpoint_names(cache_dir: str) -> list[str]:
"""Rename checkpoints saved under the old f"{size}.pt" name. Returns the new names.
Vocalinux's own downloader used to name every checkpoint after the catalog
Voculinux's own downloader used to name every checkpoint after the catalog
entry, which is wrong wherever upstream publishes a versioned file: "large"
is stored as large-v3.pt. A file left under the old name is invisible three
times over. load_model() looks for the upstream name and refetches 2.9GB,
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Whisper.cpp model information and hardware detection for Vocalinux.
Whisper.cpp model information and hardware detection for Voculinux.
This module provides model metadata and hardware acceleration detection
for whisper.cpp, supporting Vulkan, CUDA, and CPU backends.
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Version information for Vocalinux.
Version information for Voculinux.
"""
__version__ = "0.18.5"