Press a keyboard shortcut, speak, press it again — your speech is transcribed with faster-whisper and pasted wherever the cursor is. Built for GNOME on Wayland (works in terminals too).
- Toggle dictation with one shortcut (default Ctrl+Alt+D) — tap to start, tap again to stop, transcribe and paste. No push‑to‑hold.
- German and English out of the box (auto-detected), model
large-v3. - GPU (NVIDIA/CUDA) with automatic CPU fallback.
- Pastes via the clipboard so it is keyboard-layout independent (works with the Austrian QWERTZ layout, umlauts, etc.).
- A top-bar indicator shows “● REC” / “⏳ …” while recording and transcribing, and hides itself when idle.
- Linux, GNOME on Wayland (tested on Ubuntu 25.10 / GNOME 49).
- Python 3.10+.
- A microphone (PipeWire/PulseAudio default source is used).
- Write access to
/dev/uinput(for synthesizing the paste keystroke). - For the top-bar indicator: an AppIndicator/SNI GNOME extension enabled
(on Ubuntu
ubuntu-appindicators@ubuntu.com; the installer enables it). - Optional: an NVIDIA GPU + driver for fast transcription. Without one it falls back to CPU automatically.
git clone <this-repo> ~/dev/whisper-dictate
cd ~/dev/whisper-dictate
./install.sh # add --no-model to skip the ~3 GB pre-downloadinstall.sh is idempotent — re-run it after git pull. It installs the
system packages (asks for sudo), creates a private venv, installs the
scripts, pre-fetches the model, and registers the GNOME shortcut.
Override the shortcut: SHORTCUT='<Super>r' ./install.sh.
- Focus any text field (browser, editor, terminal, chat …).
- Press Ctrl+Alt+D → bell + “● Recording”. Speak.
- Press Ctrl+Alt+D again → “Transcribing…”, then the text is pasted.
Tunables (env vars; set them in ~/.local/bin/whisper-dictate):
| Var | Default | Meaning |
|---|---|---|
WD_MODEL |
large-v3 |
faster-whisper model name |
WD_DEVICE |
cuda |
cuda or cpu (auto-falls back to cpu) |
WD_COMPUTE |
auto |
CT2 compute type (auto/int8/float16/…) |
WD_BEAM |
1 |
beam size (1 = greedy, snappy) |
WD_LANG |
(auto) | force a language, e.g. de |
WD_PASTE_KEY |
shift+insert |
ydotool key sequence used to paste |
Ctrl+Alt+D ──> whisper-dictate (toggle)
press 1: ffmpeg records the default mic -> /run/user/$UID/whisper-dictate/rec.wav
writes state "recording"
press 2: stop ffmpeg -> state "transcribing" -> transcribe.py
(faster-whisper, GPU) -> text
-> wl-copy (clipboard + primary) -> ydotool Shift+Insert
-> state "idle" (cleared on every exit path via trap)
whisper-dictate-indicator (autostarted daemon)
polls the state file every 250 ms and shows / hides the top-bar icon
State and the recording live in $XDG_RUNTIME_DIR/whisper-dictate/
(tmpfs; the audio is deleted right after transcription). The indicator
is decoupled — it only reads the state file, so the wrapper never
blocks on it and either part can be restarted independently. The last
run's stderr is in ~/.local/share/whisper-dictate/last-run.log.
- Paste = Shift+Insert, on clipboard and primary. GNOME 49 Wayland
has no usable focused-window API (
IntrospectisAccessDenied, xdotool can't see Wayland-native terminals), so per-app key selection isn't possible. Shift+Insert is the one paste key that works both in VTE terminals (which paste the primary selection) and in GTK/Qt/LibreOffice/ JetBrains apps (which paste the clipboard) — so the text is put on both. - Clipboard paste, not key-typing.
ydotool typeemits US scancodes and would mangle the Austrian QWERTZ layout;wtypeneeds the virtual keyboard protocol that GNOME/Mutter does not implement. Clipboard paste is the only layout-safe route. ydotool0.1.8 is daemonless on Ubuntu (noydotoold); it opens/dev/uinputdirectly, hence the/dev/uinputaccess requirement and the--delaybefore the keystroke (the freshly created virtual input device must be enumerated by Mutter first).- Lock fd is closed (
9>&-) on every backgrounded child. bash fds fromexec N>are inherited and are not close-on-exec, soffmpeg/wl-copy/paplaywould otherwise keep theflockheld and wedge every later press as “busy”. - No
setsidfor the recorder. Under job controlsetsidmay fork, making$!the wrong PID so the recorder can never be stopped. A GNOME-spawned script has no controlling tty, so a plain&already letsffmpegoutlive the script; STOP escalates SIGINT→TERM→KILL and sweeps strays so a recorder can never run away. - Indicator is a separate polling daemon, not driven by the wrapper. A persistent panel item needs a long-lived process with a GLib loop; the short-lived toggle script can't host one. Coupling them (signals/ socket) would risk wedging the hot paste path, so the wrapper only writes a one-word state file and the daemon polls it (250 ms). GNOME Wayland has no client window positioning and Mutter implements no layer-shell, so an overlay window is out — an AppIndicator/SNI tray item (via the GNOME extension) is the only robust top-bar option, and it needs no logout once the extension is active.
large-v3 is loaded on demand each use (~10–15 s: model load + CUDA init
- inference). For near-instant transcription, run a persistent model server that stays warm in VRAM (not included here).
- “Busy — still transcribing” that never clears: a stuck recorder.
pkill -INT -f 'ffmpeg.*whisper-dictate'and remove$XDG_RUNTIME_DIR/whisper-dictate/state.lock. - Nothing pastes: check
~/.local/share/whisper-dictate/last-run.log. Confirm/dev/uinputis writable andydotool key shift+insertworks. - GPU errors in the log: it auto-falls back to CPU; check NVIDIA
driver/CUDA.
WD_DEVICE=cpuforces CPU. - Hallucinated text on silence (e.g. “das ist ein Test”): a known Whisper trait on near-silence; harmless when you actually speak.
- No top-bar icon: the AppIndicator extension isn't active. Check
gnome-extensions list --enabled | grep -i appindicator; enable it withgnome-extensions enable ubuntu-appindicators@ubuntu.com. Check the daemon runs:pgrep -af whisper-dictate-indicator.
rm -f ~/.local/bin/whisper-dictate ~/.local/bin/whisper-dictate-indicator
rm -f ~/.config/autostart/whisper-dictate-indicator.desktop
rm -rf ~/.local/share/whisper-dictate
pkill -f whisper-dictate-indicator
# then remove the "Whisper Dictate" entry in
# Settings → Keyboard → Keyboard Shortcuts