Skip to content

Development

Saco Song edited this page Jul 30, 2026 · 4 revisions

Development

简体中文 · Home

Toolchain and build

The crate uses Rust edition 2024 and commits Cargo.lock. All project targets enforce that lockfile; Cargo downloads missing locked dependencies when network access is available:

cargo build --release --locked
# Equivalent project target:
make build

make build runs cargo build --release --locked. Use cargo fetch --locked separately only when you intentionally want to pre-populate a Cargo cache.

Run the daemon from source only after stopping the installed service, because both bind the same runtime sockets:

systemctl --user stop voice-input.service
VOICE_INPUT_ASSET_DIR="$PWD/assets" cargo run --locked -- daemon

The source daemon still reads the normal user config and runtime directory. Restart the installed service when finished.

Repository layout

src/
  main.rs                 TLS provider initialization and top-level exit handling
  args.rs                 hand-written CLI parsing/help
  app.rs                  command dispatch and Settings activation/launch
  daemon.rs               control socket, capture service, session pipeline
  config.rs               public TOML schema, defaults, legacy migration
  credentials.rs          systemd/environment resolution and stdin encryption
  agent_context.rs        focused Pi/Codex validation and sanitization
  backend.rs              ASR trait, controls, and events
  backend/
    local_cli.rs           /usr/bin/voxtype adapter
    qwen_realtime.rs       Alibaba WebSocket streaming adapter
    qwen_batch.rs          Qwen full-audio compatible-HTTP pass
    text.rs                transcript extraction and OpenCC conversion
  llm.rs                  refinement prompt, shared deadline, fail-open behavior
  output.rs               Wayland/XWayland target detection and text emission
  state.rs                state machine snapshot and atomic persistence
  waveform.rs             PCM analysis, ASR packetizer, Unix socket publisher
  wav.rs                   temporary PCM16 WAV writer
  paths.rs, setup.rs       installed paths and setup helpers
assets/
  config.toml              canonical public sample/default reference
  voice-input*.service    systemd user-unit templates
  quickshell/              resident QML HUD
  quickshell-settings/     on-demand FloatingWindow Settings QML
  pi/                      Pi session-registry extension
  omarchy-*.conf/jsonc     Hyprland and Waybar snippets
.github/workflows/ci.yml   CI checks
packaging/                 currently empty placeholder
contrib/omarchy/           currently empty Hyprland/Waybar placeholders

Keep assets/config.toml, Rust defaults, and Settings defaults synchronized when changing a public field. The resident HUD and on-demand Settings are separate Quickshell configurations with different lifecycles.

Test suite

Run the complete local validation target, which checks QML and runs the Rust formatter, all-target check, tests, and Clippy with warnings denied:

make validate
make hud-shaders QSB=/usr/lib/qt6/bin/qsb

Set QMLLINT or QSB explicitly if those Qt tools are installed elsewhere. CI performs the same Rust checks, parses every QML asset, builds and inspects the HUD's cross-RHI .qsb shader, and rejects obsolete runtime assets and source references so the removed desktop stack cannot return accidentally.

Current unit tests are embedded in modules and cover, among other things:

  • current and legacy TOML parsing;
  • local-backend transcript extraction from plain and logged output;
  • Qwen event parsing and transcript assembly;
  • LLM response validation, retry policy, OpenRouter sorting, and the shared 1–30 second budget with five-second fallback reservation using local mock HTTP servers;
  • Pi/Codex JSONL extraction, redaction, and truncation;
  • waveform chunk independence, symmetry, NDJSON framing, and ASR packet preservation;
  • Wayland/XWayland effective output mode and the 120-character paste threshold;
  • credential trimming and NUL rejection;
  • versioned Settings NDJSON, exact-source revision conflicts, full-field validation, atomic save permissions, credential keep/replace, and separate restart-failure reporting.

Tests do not exercise a live microphone, compositor, Alibaba/OpenAI-compatible account, real Kitty/Pi/Codex process, Quickshell rendering/IPC activation, or actual clipboard restoration. Those require manual integration checks.

Manual integration checklist

Use non-sensitive test text and a disposable clipboard value.

  1. Start both user services and confirm all three runtime files/sockets exist.
  2. Test record start, stop, toggle, and cancel from a terminal.
  3. Test the intended Hyprland binding, including modifier release.
  4. Verify realtime partial text, Server VAD waveform visibility, and silent cancellation.
  5. Run a recording past the configured duration limit and confirm it stops and finalizes automatically. Under simulated realtime backpressure, confirm capture/HUD remain responsive and incomplete remote text is recovered from the full buffer.
  6. If enabled, verify final-pass replacement and /usr/bin/voxtype fallback separately.
  7. Exercise short Wayland direct type, long Wayland paste, and an XWayland target.
  8. Confirm clipboard restoration and Fcitx5 restoration.
  9. Move/reset the HUD and test a monitor focus change/hotplug.
  10. Restart Pi after installing the extension, then test Pi and Codex context independently.
  11. Open Settings twice and verify the second command activates the existing FloatingWindow; close it and verify the on-demand instance exits.
  12. Test a stale revision conflict, a config-only save, credential keep/replace, Test LLM with entered/store credentials, and a separately reported daemon restart failure.
  13. Review timing logs and Settings diagnostics for accidental content before attaching them to a report.

Design rules to preserve

  • Fail open to recognized text: LLM failure must not discard valid ASR output.
  • One refinement deadline: contextual work and fallback share one budget capped at 30 seconds; normal budgets reserve five seconds for transcript-only recovery.
  • No capture backpressure: realtime ASR and waveform/HUD clients must not block capture. A lagging bounded queue must fail into complete-buffer recovery rather than silently accepting incomplete text.
  • Explicit backend identity: local fallback remains a configured executable, defaulting to /usr/bin/voxtype.
  • Secrets stay on stdin: Settings replacements travel QML stdin → Rust → systemd-creds stdin, never config/argv/environment/log/response; field clearing in managed QML/JavaScript memory remains best effort.
  • Rust owns persistence: keep validation, 0700/0600 permissions, atomic replacement, full-field preservation, and exact-source conflict checks out of QML.
  • Validate external context: retain process/session/path checks, size limits, redaction, and prompt isolation.
  • Atomic state: concurrent writers must remain serialized through StateHandle and temporary-file replacement.

Packaging

There is currently no distro package recipe in packaging/; installation is implemented by the Makefile. make install:

  • installs one release binary to $(PREFIX)/bin (default ~/.local/bin);
  • installs the resident HUD and on-demand Settings assets to $(PREFIX)/share/voice-input;
  • installs the Pi extension under ~/.pi/agent/extensions;
  • renders unit templates into ~/.config/systemd/user;
  • creates private config and encrypted-credential directories;
  • creates a sample config only when none exists;
  • may migrate specifically recognized older Voxtype config/credential files;
  • removes obsolete installed hud.py and settings.py files during upgrades.

PREFIX changes binary/share destinations, but the service, Pi extension, config, and credential locations have separate Make variables and user-home defaults. Packagers should set all relevant variables or stage files explicitly rather than assuming PREFIX relocates everything.

Service templates contain @VOICE_INPUT_BIN@, @VOICE_INPUT_ASSET_DIR@, and @VOICE_INPUT_QUICKSHELL_DIR@; render them to final absolute paths. Keep /usr/bin/qs and optional external command dependencies visible in package metadata. Do not package local configs, encrypted blobs, runtime state, recordings, JSONL sessions, or logs.

Contribution workflow

The repository's contribution guide defines the current Rust, QML, shader, bounded-resource, error-handling, and validation standards.

  1. Open an issue or focused proposal for behavior that affects privacy, provider protocols, or desktop assumptions.
  2. Create a small topic branch and keep unrelated local configuration out of the patch.
  3. Add or update unit tests for pure logic. For desktop/network behavior, document a reproducible manual test.
  4. Update assets/config.toml, Settings, Rust defaults, README, and both Wiki languages when public behavior changes.
  5. Run make validate, compile the HUD shader, and run git diff --check as described in the contribution guide.
  6. Inspect the diff for credentials, user-specific absolute paths, transcripts, session files, recordings, and generated artifacts.
  7. Submit a pull request explaining the reason, compatibility impact, privacy/data-flow change, and test evidence.

CI runs on pushes and pull requests with read-only repository contents permission. It checks Rust formatting, all targets, tests, Clippy, QML parsing, cross-RHI shader assets, and rejects obsolete desktop-runtime assets/references; passing CI does not replace the manual compositor/audio/provider/Quickshell checks above.

See also: Architecture · Security and Privacy

Clone this wiki locally