Files
voculinux/docs/ARCHITECTURE.md
T
soubarnak ea7be5f111 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
2026-11-06 01:29:39 +05:30

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.py composes the application services and callbacks.
  • audio/capture.py and audio/pipewire.py provide capture sources.
  • speech_recognition/ owns VAD, segment processing, command handling, and engine implementations under engines/.
  • post_processor.py runs configured transcript processing scripts.
  • text_injection/ contains TextInjector, focused-window detection, IBus, and Remote Desktop Portal support.
  • ui/ contains the tray, Settings, dictation pad, history, and keyboard backend modules.
  • dbus_service.py exposes 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_add where 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. TextInjector protects 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.