Minimal voice dictation for Linux: press a shortcut, speak, press it again, and the text lands in the focused window. No daemon, nothing running between dictations.
- records the microphone and encodes MP3 on the fly;
- transcribes by calling any command you provide, so you can plug in a cloud API or a local model — the one shipped here is Groq Whisper;
- shows a pill while it runs: the input waveform, a spinner while it transcribes, the verdict on a cancel;
- pastes the transcript into the focused window;
- notifies failures;
- keeps every dictation on disk and lets you list, show, copy, re-transcribe and delete them.
tiny-dictate is a single binary. Download the archive for your architecture from the
releases page, unpack it, and put tiny-dictate
and tiny-dictate-transcribe on your PATH.
On Arch, yay -S tiny-dictate-bin (or any AUR helper) does that and pulls the runtime libraries.
At runtime it needs:
arecord— recording (alsa-utils)lame— MP3 encodingydotool— pastingnotify-send— failure notifications (libnotify)wl-clipboard— the clipboardcurl— the reference transcriber- the
gtk4andgtk4-layer-shellshared libraries — the pill - a compositor with
wlr-layer-shell, for the pill only: sway, Hyprland, niri, KDE. Without one a dictation still works, nothing is drawn on screen. GNOME/Mutter has no layer-shell.
To build it yourself instead, see docs/development.md.
Any desktop environment that binds a key to a command can drive it; example with sway/i3:
bindsym $mod+backslash exec tiny-dictate toggle
bindsym $mod+Shift+backslash exec tiny-dictate cancel
toggle starts and stops; cancel needs its own key, so that a dictation ended by accident can be
cancelled without being transcribed.
tiny-dictate has no transcription backend of its own: it calls a command you provide. That command
receives the audio file as $1, writes the transcribed text on stdout, and exits non-zero on
failure, in which case its stderr is shown to you. It is resolved with command -v:
$TINY_DICTATE_TRANSCRIBE if set, otherwise tiny-dictate-transcribe on PATH.
The transcriber shipped here, transcribers/groq, calls Groq Whisper:
export GROQ_API_KEY=... # create one at https://console.groq.com/keys
export GROQ_WHISPER_MODEL=... # optional, defaults to whisper-large-v3Plug in anything else — another service, a local whisper.cpp, your own script — by pointing
TINY_DICTATE_TRANSCRIBE at it:
export TINY_DICTATE_TRANSCRIBE=~/.local/bin/my-transcriberA transcriber can declare the largest audio file it accepts by answering --print-audio-limit with a
byte count; the recording then stops early enough that the file handed to it stays under that limit.
A transcriber that declares nothing is unbounded.
Whisper sometimes answers "." to audio that holds no speech; the reference transcriber drops the
segments it believes are not speech, and a transcript that holds no word at all.
A transcriber is a plain script, so it can fetch its credentials wherever you keep them — it runs from a shortcut, with no terminal, so the lookup must not prompt. Wrap the reference one:
#!/usr/bin/env bash
# ~/.local/bin/tiny-dictate-transcribe
GROQ_API_KEY="$(secret-tool lookup Title 'Groq API key')" exec ~/path/to/transcribers/groq "$@"$XDG_CONFIG_HOME/tiny-dictate/config.toml (~/.config/tiny-dictate/config.toml by default) is
optional, and every key has a default:
keep = 10 # the past sessions retained, older ones pruned after a dictation
color = "#e4e4e7" # the pill's waveform
clipboard_seconds = 60 # how long the transcript stays offered on the clipboard, 0 means no expiryA missing, unreadable or malformed file, or an unknown key, falls back to the defaults and never blocks a dictation. The file holds no secret.
Usage: tiny-dictate <command>
Commands:
start Start recording
stop Stop recording and transcribe
cancel Cancel recording
toggle Start if idle, stop if recording
status Show status (idle or working)
session List the kept sessions, or browse them on a terminal
session list [--output text|json] List the kept sessions, newest first
session browse Browse the kept sessions on a terminal
session show <id|last> [--output text|json] Show one session: its fields, its path and its transcript
session copy <id|last> Copy a session's transcript to the clipboard
session transcribe <id|last> [--print] Transcribe a session again, to the clipboard or to stdout
session delete <id|last> Remove exactly one session
Every dictation is kept as a session under $XDG_STATE_HOME/tiny-dictate/sessions: the recording,
the transcript, the provider's raw response and the metadata, one directory per dictation. session
lists them or opens a browser on a terminal; session show reads one in full; session copy puts
its transcript on the clipboard, transcribing it first when it has none; session transcribe runs
the transcriber again; session delete is the only command that destroys one.
contrib/waybar/tiny-dictate-module.jsonc shows the dictation state in the bar, and a click toggles
it:
cp contrib/waybar/tiny-dictate-module.jsonc ~/.config/waybar/Then include the fragment and add custom/tiny-dictate to a bar.
When the shortcut is a chord your compositor cannot see, or a double tap, a key remapper can turn it
into a plain function key. On the reference setup a double tap of Super emits F19 and a tap with
Shift held emits F24, bound as tiny-dictate toggle and tiny-dictate cancel. Prefer F24 over
F20 (the evdev keymap maps F20 to XF86AudioMicMute) and over Shift+F19 (the compositor cannot
see a Shift the keyd layer has not emitted).
Building from source, the tests and the pill preview, the glossary, and the design decisions.