Skip to content

Development

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

Development

简体中文 · Home

Toolchain and build

The crate uses Rust edition 2024 and commits Cargo.lock. Fetch the locked dependency set once, then the Makefile can build offline:

cargo fetch --locked
cargo build --release --locked
# Equivalent project target after dependencies are cached:
make build

make build runs cargo build --release --offline. A fresh checkout with an empty Cargo cache must run cargo fetch --locked first.

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 --offline -- 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 status/config/settings clients
  daemon.rs               control socket, capture service, session pipeline
  config.rs               public TOML schema, defaults, legacy migration
  credentials.rs          systemd/environment credential resolution
  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/              primary QML HUD
  hud.py                   legacy GTK4 layer-shell HUD
  settings.py              optional GTK/libadwaita settings UI
  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. Remember that the primary Quickshell HUD and legacy Python HUD are separate implementations.

Test suite

Run the same Rust checks as CI:

cargo fmt --all -- --check
cargo check --locked --all-targets
cargo test --locked

Validate Python syntax without writing cache files into the worktree:

PYTHONPYCACHEPREFIX=/tmp/voice-input-pycache \
  python3 -m py_compile assets/hud.py assets/settings.py

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–5 second budget 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.

Tests do not exercise a live microphone, compositor, Alibaba/OpenAI-compatible account, real Kitty/Pi/Codex process, Quickshell rendering, 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. If enabled, verify final-pass replacement and /usr/bin/voxtype fallback separately.
  6. Exercise short Wayland direct type, long Wayland paste, and an XWayland target.
  7. Confirm clipboard restoration and Fcitx5 restoration.
  8. Move/reset the HUD and test a monitor focus change/hotplug.
  9. Restart Pi after installing the extension, then test Pi and Codex context independently.
  10. Review timing logs 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 the same maximum five-second budget.
  • No visual backpressure: waveform/HUD clients must not block capture or ASR.
  • Explicit backend identity: local fallback remains a configured executable, defaulting to /usr/bin/voxtype.
  • Secrets out of config/argv/logs: new credentials should use the runtime resolver and systemd credential IDs.
  • 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 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.

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

  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 formatting, all-target check, tests, and Python syntax validation.
  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, and Python syntax; passing CI does not replace the manual compositor/audio/provider checks above.

See also: Architecture · Security and Privacy

Clone this wiki locally