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
5.2 KiB
Architecture
Voculinux is a GTK 3 desktop application. main.py wires together the
configuration manager, recognition manager, tray UI, D-Bus service, and text
injector. Audio capture and recognition run outside the GTK event loop.
Module map
main.pycomposes the application services and callbacks.audio/capture.pyandaudio/pipewire.pyprovide capture sources.speech_recognition/owns VAD, segment processing, command handling, and engine implementations underengines/.post_processor.pyruns configured transcript processing scripts.text_injection/containsTextInjector, focused-window detection, IBus, and Remote Desktop Portal support.ui/contains the tray, Settings, dictation pad, history, and keyboard backend modules.dbus_service.pyexposes desktop control methods.gateway_embed/manages the optional local VocaGateway integration.
Dictation data flow
PortAudio or PipeWire
|
v
audio/capture.py and audio/pipewire.py
|
v
recognition_manager.py: VAD and silence segmentation
| |
| Silero VAD, when available
| amplitude VAD otherwise
v
bounded segment queue
|
v
recognition worker -> selected speech engine -> corrections and commands
|
v
text callback -> post_processor.py -> TextInjector backend
PortAudioCaptureSource and the PipeWire source provide mono 16 kHz PCM chunks.
The recognition manager owns dictation segmentation. It uses Silero VAD when
available and amplitude thresholds otherwise, then places finalized segments
on a bounded queue. A recognition worker consumes the queue, dispatches each
segment to the selected engine, applies dictionary corrections and voice
command parsing, and invokes callbacks.
The text callback submits work to one ordered post-processing worker. That
worker runs the configured post-processing script, then injects text or places
it in the in-app dictation pad. TextInjector selects a backend from the
detected DesktopEnvironment: X11 uses xdotool, Wayland can use wtype or
ydotool, and the IBus and Remote Desktop Portal paths support their respective
sessions.
Threads and state ownership
- The GTK main thread owns widgets, dialog state, tray state, and the GLib main
loop. UI-facing state callbacks use
GLib.idle_addwhere needed; the D-Bus control service also marshals Toggle, Start, and Stop calls to this loop. - The audio capture thread reads the capture source, tracks the current audio buffer and VAD state, and enqueues completed segments. The recognition worker owns queue consumption and transcription callback execution.
- The recognition manager's model lock protects engine and recognizer access during transcription, initialization, and cleanup. The segment queue is thread-safe; capture-buffer handoff uses its buffer lock.
- Text callbacks enqueue work on a single post-processing worker so script
execution and injection stay in spoken order. Clipboard restoration can run
on a separate worker.
TextInjectorprotects its injection and clipboard restore bookkeeping with locks and generation state. - Keyboard backends use listener or monitor threads for pynput and evdev. IBus and Remote Desktop Portal injection also use worker threads for their input and D-Bus work.
- GLib timeouts drive UI polling and progress updates. Recognition and capture workers use their own queue and event waits rather than GTK timers.
Speech engines
common_types.SpeechEngine defines the runtime-checkable engine contract.
speech_recognition/engines/available_engines() reports optional engines in
Settings display order without importing the large Whisper packages just to
probe availability. SettingsDialog retains a compatibility wrapper for its
legacy engine-to-availability mapping.
SpeechRecognitionManager._process_audio_buffer() uses an engine-name dispatch
table to call the existing per-engine transcription methods. The manager owns
engine selection, model lifecycle, segment language snapshots, and the model
lock. Optional engine packages are loaded only when their engine is selected.
Configuration and Settings
ui/config_manager.py defines DEFAULT_CONFIG, loads the user's JSON config,
and recursively overlays saved values on those defaults. It persists changes
through save_config(). Settings controls generally apply changes as they are
made and use callbacks to update the running engine or related services; values
that require a model reload take effect during that reload.
The Settings UI is split across settings_dialog.py, settings_about.py, and
settings_engines.py. Other UI responsibilities live in focused modules such
as tray_indicator.py, dictation_overlay.py, transcription_history.py,
keyboard_shortcuts.py, and keyboard_backends/.
Services and optional integrations
dbus_service.py owns the com.voculinux.Voculinux session-bus name and
provides Toggle, Start, and Stop methods for external controls.
gateway_embed/ manages the optional local VocaGateway container lifecycle,
status, pairing, and configuration. It is separate from the default
on-device whisper.cpp engine.