Audio version — Estimated duration: 5 min 49 sec

Architecture

This page explains every major piece of Whisper Dictate — what each component does, how they connect, and how data flows through the system.

High-Level Overview

Whisper Dictate is built around five main subsystems that work together in sequence:

  1. Keyboard Monitor — Watches for your hotkey presses
  2. Audio Recorder — Captures microphone input while the hotkey is held
  3. Whisper Model — Converts speech audio into text
  4. Text Injector — Types the transcribed text into your focused application
  5. Notifications — Shows desktop popups for status updates

These subsystems run concurrently using multiple threads, coordinated by shared state protected by a lock (a standard threading safety mechanism that prevents two threads from modifying the same data at the same time).

System Block Diagram

The diagram below shows how data flows from your keyboard press all the way to text appearing on screen. Each box represents a piece of code or an external tool, and the arrows show the direction of data flow.

graph LR
    A[Keyboard Input] -->|evdev reads<br/>raw events| B[Hotkey State Machine]
    B -->|key down: start<br/>recording| C[Audio Recorder]
    B -->|key up: stop<br/>recording, transcribe| D[Transcription Worker]
    C -->|audio chunks| E[Audio Buffer]
    E -->|every 2 seconds<br/>for preview| F[Stream Preview]
    E -->|entire audio<br/>snapshot| D
    D -->|audio data| G[Whisper Model<br/>faster-whisper]
    G -->|transcribed text| H{Is ydotool<br/>available?}
    H -->|yes| I[ydotool types<br/>into app]
    H -->|no| J[wl-copy copies<br/>to clipboard]
    F -->|preview text| K[Terminal output]
    I -->|keystrokes| L[Any focused<br/>application]
    J -->|Ctrl+V needed| L

Components Explained

1. Keyboard Monitor (evdev)

What it does: Reads raw input events directly from Linux kernel input devices (/dev/input/event*). When you press or release a key, the kernel sends an event, and evdev delivers it to our code.

Why this matters: Unlike X11 applications that can listen for global hotkeys through the windowing system, Wayland applications cannot do this for security reasons. By reading /dev/input/ directly, we bypass the windowing system entirely and respond to the hotkey at the kernel level — which works on any desktop environment, Wayland or X11.

What happens: The code finds all real keyboards on your system (filters out media remotes and other input devices that don’t have letter keys), then runs an async loop that waits for events from each one. When it sees our hotkey being pressed or released, it calls the appropriate handler.

2. Hotkey State Machine

What it does: Tracks whether the hotkey is currently held down, and decides what actions to take on press and release.

Why this matters: The kernel sends three types of key events: press (value=1), release (value=0), and repeat (value=2 — fires repeatedly while a key is held). Without proper state tracking, every repeat event would look like a new press, causing multiple recordings to start.

How it works: A boolean flag _hotkey_held is set to True on first press and back to False on release. The repeat events (value=2) are simply ignored because _hotkey_held is already True, and the press handler returns early.

3. Audio Recorder (sounddevice)

What it does: Opens your microphone and captures audio data at 16kHz (16,000 samples per second) mono as 32-bit floating point values.

Why this matters: Whisper expects audio at exactly 16kHz sample rate. sounddevice is used because it provides low-latency access to PortAudio (the same library that powers most Linux audio applications) and supports non-blocking callback mode — meaning audio capture runs in the background without blocking the rest of the code.

How it works: A callback function fires each time a new chunk of audio is available. It simply copies the data and appends it to a list. This runs in a separate thread that lives only while the hotkey is held.

4. Stream Preview

What it does: Every 2 seconds while you’re still holding the hotkey, a thread is spawned that transcribes whatever audio has been captured so far and prints it to the terminal with a >> prefix.

Why this matters: Without this, you’d have no feedback during recording — you’d hold the key, speak, and only see the result after releasing. The stream preview lets you know it’s working and you can see partial results in real time.

Critical design choice: The stream preview only prints to the terminal. It never types or injects text. This is intentional — typing while the hotkey (Right Alt) is still held would send keystrokes with Alt modifier active, triggering AltGr combinations or desktop shortcuts (like Alt+PrtSc for screenshot). This was a real bug that was fixed.

5. Transcription Worker

What it does: When you release the hotkey, this takes the entire audio recording, feeds it to Whisper, and handles the result.

Why this matters: Unlike the stream preview that only transcribes partial audio, the transcription worker has the complete recording — meaning Whisper has maximum context for accurate transcription.

How it works: The key-up handler grabs a snapshot (a copy) of the audio buffer before resetting anything, then spawns a thread that:

  1. Concatenates all audio chunks into one array
  2. Checks if the recording is long enough (≥0.3 seconds)
  3. Sends it to Whisper for transcription
  4. Checks that the hotkey hasn’t been pressed again (if it has, use clipboard instead of typing)
  5. Types or copies the result

6. Text Injector (ydotool / wl-copy)

What it does: Takes the transcribed text and either types it into the focused application using ydotool, or copies it to the clipboard using wl-copy as a fallback.

Why this matters: On Wayland, the old X11 method of injecting text (xdotool) doesn’t work. ydotool uses the Linux uinput kernel subsystem to inject keystrokes at the kernel level, which works on any display server. However, it requires a daemon (ydotoold) to be running. If that fails, wl-clipboard (wl-copy) is used as a fallback — the text is copied and a notification tells you to press Ctrl+V.

Data Flow Summary

Here’s the complete journey of a single dictate session:

1. KEY PRESSED
   evdev detects Right Alt press
   → Hotkey state machine sets _hotkey_held = True
   → Audio buffer is cleared for new recording
   → Recording thread starts
   → Desktop notification: "Recording..."

2. WHILE KEY HELD
   Microphone captures audio chunks → appended to buffer
   Every 2 seconds:
     → Thread spawned to transcribe new chunks
     → Partial text printed to terminal (>> prefix)
     → No typing happens during this phase

3. KEY RELEASED
   evdev detects Right Alt release
   → Hotkey state machine sets _hotkey_held = False
   → Recording thread stops
   → Snapshot of full audio buffer is taken
   → Transcription thread is spawned

4. TRANSCRIPTION
   Audio chunks concatenated into single array
   → Check minimum duration (≥0.3s)
   → Whisper model transcribes audio → text
   → Text printed to terminal (-> prefix)

5. TEXT INJECTION
   Check if hotkey was pressed again (race condition guard)
   → If not held: ydotool types text into focused app
   → If held (new recording started): wl-copy to clipboard instead
   → Desktop notification: "Typed" or "Copied to clipboard"

6. READY FOR NEXT
   Back to step 1, waiting for next hotkey press

Now that you understand the architecture, you can dive deeper into the data-flow to see the exact sequence with timing, or threading to understand how threads coordinate and what problems were solved.