-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
The normal desktop deployment has two long-running processes:
-
voice-input daemonowns audio capture, session control, ASR, state, refinement, and output. -
/usr/bin/qs --no-duplicate --path …/quickshellrenders the HUD.
Clients send short commands over $XDG_RUNTIME_DIR/voice-input/control.sock. Runtime state is atomically replaced at $XDG_RUNTIME_DIR/voice-input/state.json; waveform NDJSON is broadcast separately over waveform.sock.
flowchart LR
Key[Hyprland binding] -->|control.sock| D[Daemon]
PW[pw-record<br/>mono PCM16] --> D
D --> RT[Qwen realtime WebSocket]
D --> Local[/usr/bin/voxtype]
D --> Final[Qwen full-audio HTTP]
D --> LLM[OpenAI-compatible LLM]
D -->|state.json| HUD[Quickshell HUD]
D -->|waveform.sock| HUD
D --> Out[wtype / wl-copy<br/>xclip / xdotool]
pw-record produces mono signed 16-bit PCM at audio.sample_rate (16 kHz by default). With pre-roll disabled, each session owns a dedicated recorder. With pre-roll enabled, a capture service keeps one recorder hot, maintains a ring buffer, and attaches the active session to that shared stream. The ring holds at least the configured pre-roll window and the 320 ms capture warm-up.
At start, the daemon also records a Wayland/XWayland target hint and, when enabled, starts focused-agent discovery on a separate thread.
For alibaba-qwen-realtime, PCM is packetized into 2,048-sample chunks and sent to Qwen over WebSocket. Realtime events update committed and unstable transcript text. server-vad supplies speech-start and speech-stop events; manual commits at stop. If Server VAD produces no transcription activity for three seconds while enough audio is buffered, the client forces a buffer commit to recover incremental output.
The waveform analyzer works directly from PCM. It uses a 512-sample window and 256-sample hop, producing 62.5 frames per second at 16 kHz. Thirty bars are mirrored around the center. Visibility is gated by realtime voice activity; waveform cadence is independent of ASR packet cadence.
For local-cli, a background partial thread periodically transcribes the accumulated audio according to audio.partial_interval_ms. On stop, the complete audio is transcribed again.
On stop, capture detaches or the dedicated pw-record child is terminated, the final partial ASR packet is flushed, and realtime ASR is asked to finish. A realtime session with no speech event or non-empty transcript after a 350 ms grace period becomes a silent cancellation: no final ASR, LLM call, or text injection occurs.
If final_pass_enabled = true, the daemon writes the complete PCM buffer to a temporary WAV and sends it as a base64 data:audio/wav input to the configured Qwen final model. Success replaces realtime text. Failure uses realtime final text when available, then /usr/bin/voxtype when local fallback is enabled. Without a final pass, usable realtime final text is preferred and local fallback handles remote failure or empty output.
Chinese output runs through OpenCC (t2s or s2t) after recognition.
Refinement is conservative and optional. The default llm.timeout_ms = 5000; implementation clamps the shared total budget to 1,000–5,000 ms. Prompt construction, the contextual request, and any eligible transcript-only fallback all consume the same deadline. A context-free retry occurs only for recognized context/payload errors or an invalid response, and only if at least one second remains.
Any error, timeout, truncated response, missing model/credential, or exhausted budget leaves the ASR transcript unchanged. Timing logs record attempt kind, elapsed milliseconds, outcome category, and final choice; they do not print transcript or context.
The daemon resolves the active target again while retaining the target hint captured at start. Direct type mode uses wtype. Text longer than 120 Unicode characters automatically switches to paste. XWayland switches from type to paste by default. Wayland paste uses wl-copy plus a wtype key chord; XWayland uses xclip plus xdotool. Paste mode backs up and restores the relevant clipboard after a 220 ms wait.
When configured, an Fcitx5 guard temporarily closes the active input method before output and restores it afterward.
| Boundary | Why it exists |
|---|---|
| Capture service or dedicated reader thread | Reads PipeWire continuously without waiting for network or UI work. |
| Qwen backend worker + realtime event pump | WebSocket I/O and state/transcript updates progress independently. |
| Local partial-ASR thread | Periodic previews do not run in the control listener. |
| Agent-discovery thread | Kitty/Hyprland inspection overlaps recording. |
| Waveform publisher thread | Uses a bounded queue and nonblocking sends; full queues may drop visual frames instead of blocking capture. |
| Serialized state update lock | Keeps concurrent capture and ASR updates ordered; each JSON snapshot uses temporary-file rename. |
| Quickshell process | Polls state every 50 ms and consumes waveform socket frames independently. HUD failure does not own recognition. |
The control listener processes one accepted connection at a time and holds the daemon mutex through finalization, refinement, and output. Toggle requests include a timestamp; a queued toggle older than 750 ms is ignored so a delayed second press cannot start a new session after processing completes.
idle → arming → recording → transcribing → refining → outputting → idle, with error available from any failed control/session operation. Refining is skipped when disabled. Silent cancellation returns directly to idle.
See also: Configuration · Agent Context · Desktop Integration
English Home · 简体中文首页 · Source repository · MIT License
Voice Input is an independent community project. HUD and Settings require Quickshell 0.3+. Review Security and Privacy before enabling remote ASR, LLM refinement, pre-roll, agent context, or replacing credentials in Settings.
Voice Input 是独立的社区项目。HUD 和 Settings 需要 Quickshell 0.3 或更高版本。启用远程 ASR、LLM refinement、pre-roll、Agent 上下文,或者在 Settings 中替换 credential 前,请阅读安全与隐私。