Talkat is voice dictation for Wayland desktops. Press a hotkey, talk, and your words are typed into whatever window has focus. Speech recognition runs locally (faster-whisper or Vosk) in a background service, so your audio never leaves your machine.
Press the hotkey and talkat starts recording. As you talk, it cuts the audio at natural pauses, transcribes each piece in the background, and types it as soon as it's ready. Press the hotkey again to stop: the rest is transcribed and typed, and the transcript is saved. The only thing that can leave your machine is transcript text, and only if you point AI post-processing at a hosted LLM.
Talkat runs on Linux with a Wayland compositor and Python 3.11+. On niri, sway and Hyprland it also checks that focus hasn't moved before typing.
Install · Quick start · Usage · Configuration · Troubleshooting
yay -S talkat # or: paru -S talkat
sudo pacman -S --needed wl-clipboard libnotify # optional: clipboard fallback, notificationsThe package pulls in python, portaudio and ydotool. Do the
one-time ydotool setup, then start the model
server:
systemctl --user enable --now talkatThe package ships its own unit (/usr/lib/systemd/user/talkat.service), so
don't run talkat install-service on top of it.
You need:
- uv
- ydotool 1.0 or newer, including the
ydotoolddaemon - PortAudio headers and a C compiler, because PyAudio builds from source:
- Debian/Ubuntu:
sudo apt install portaudio19-dev build-essential python3-dev - Fedora:
sudo dnf install portaudio-devel gcc python3-devel
- Debian/Ubuntu:
- Optional:
wl-clipboard(orxclip) for the clipboard fallback, andlibnotify(notify-send) for status notifications
git clone https://github.com/ronakrm/talkat.git
cd talkat
./setup.shsetup.sh installs the talkat command into an isolated environment with
uv tool install, so it lands in ~/.local/bin. It then runs
talkat install-service, which writes ~/.config/systemd/user/talkat.service
and enables and starts it. Run it as your normal user, not root. Use one
install method per machine: if the AUR package is already installed,
setup.sh warns you and asks before shadowing it.
Talkat types through ydotool. Its daemon, ydotoold, needs write access to
/dev/uinput, and talkat's modifier guard reads key state
from /dev/input. The input group gives both:
sudo usermod -aG input "$USER"
echo 'KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"' \
| sudo tee /etc/udev/rules.d/80-uinput.rules > /dev/nullReboot so the group change and the udev rule both take effect. Then keep
ydotoold running, either way works:
- Arch's ydotool package ships a user service:
systemctl --user enable --now ydotool - Or start it from your compositor: niri
spawn-at-startup "ydotoold", swayexec ydotoold, Hyprlandexec-once = ydotoold
| AUR | setup.sh | |
|---|---|---|
| Upgrade | yay -Syu talkat, then systemctl --user restart talkat |
git pull && ./setup.sh (it restarts the service) |
| Uninstall | systemctl --user disable --now talkat, then sudo pacman -R talkat |
talkat uninstall-service, then uv tool uninstall talkat |
After an upgrade, talkat doctor should report the same version for the
command and the server. Uninstalling leaves your settings, models and
transcripts in ~/.config/talkat, ~/.cache/talkat and
~/.local/share/talkat; delete those to remove everything.
-
Calibrate. Stay quiet for 10 seconds while it runs:
talkat calibrate
This measures your room's noise and saves a speech threshold to
~/.config/talkat/config.json. Talkat uses the threshold to find the pauses where it cuts your dictation. Run it again when you change microphones or rooms. -
Bind a hotkey to
talkat listen. The same key starts and stops recording, so make sure holding it down doesn't repeat the command.niri (inside your
binds { }block):Mod+Apostrophe repeat=false { spawn "talkat" "listen"; } Mod+Shift+Apostrophe repeat=false { spawn "talkat" "listen" "--to-file"; }
sway:
bindsym --no-repeat $mod+apostrophe exec talkat listen bindsym --no-repeat $mod+Shift+apostrophe exec talkat listen --to-fileHyprland (plain
binddoesn't repeat; don't usebinde):bind = SUPER, apostrophe, exec, talkat listen bind = SUPER SHIFT, apostrophe, exec, talkat listen --to-fileThe second binding is optional. It takes long-form notes into a file instead of typing (see
--to-file). If you installed withsetup.shand your compositor can't findtalkat, use the full path~/.local/bin/talkat. -
Dictate. Focus a text field, press the hotkey, talk, and press it again. The first time the service starts it downloads the default model (
small.en, a few hundred MB), so give it a minute.journalctl --user -u talkat -fshows the progress. -
If something's off, run
talkat doctor(see Troubleshooting).
talkat listen # start recording
talkat listen # stopWhile talkat records:
- It types as you talk. The recording is cut at natural pauses, and each piece is transcribed in the background and typed as soon as it's ready, usually a second or two after you finish a sentence.
- Pauses don't stop it. Recording ends when you toggle it off, after
idle_timeout(60 s) with no speech, or atmax_recording_duration(10 minutes, a safety net). While it's quiet, a notification everyidle_notify_interval(30 s) reminds you it's still recording. Another tells you when it stops on its own. - It keeps a transcript in
~/.local/share/talkat/transcripts/<YYYYMMDD_HHMMSS>_dictation.txt(seesave_transcriptsandtranscript_dir).
listen options:
| Option | Effect |
|---|---|
--to-file |
Long-form notes: append to a file instead of typing (below) |
-o FILE |
Write the transcript to FILE when you stop, instead of typing |
--postprocess PROFILE |
Run the transcript through an AI post-processing profile before it's typed |
--language CODE |
Language for this run (es, de, auto, …) |
--max-recording SECONDS |
Recording cap for this run (overrides max_recording_duration) |
--http-timeout SECONDS |
Model server request timeout for this run (overrides http_timeout) |
--try-lock |
For scripts: fail instead of stopping a running recording or waiting for another talkat command |
With -o, --postprocess, or "output_mode": "clipboard", the transcript
is delivered once when you stop, not typed as you talk.
Talkat never silently loses a transcript. A notification tells you whenever text goes somewhere other than the window you were typing in.
- Focus guard. Text goes only to the window that had focus when you
started. If focus moves (alt-tab, a popup), typing stops and the rest goes
to the clipboard. This works on niri, sway and Hyprland and is off on other
compositors. Turn it off with
"focus_guard": false. - Modifier guard. Keys that talkat types combine with keys you're
holding, so with Super held down every typed letter becomes a compositor
shortcut. Talkat types one key at a time and pauses while Ctrl, Shift, Alt
or Super is held. If one stays held for 5 seconds, the rest goes to the
clipboard. This guard needs the
inputgroup (see ydotool setup). - Clipboard fallback. If ydotool is missing or
ydotooldisn't running, the transcript goes to the clipboard (wl-copy, elsexclip). With neither installed, it's printed to standard output. - Saved audio. If a piece still can't be transcribed after retries (the
model server is down or keeps failing), its audio is saved under
~/.local/share/talkat/untranscribed/and typing stops. Everything from that point goes to the clipboard, with a marker in place of the missing part, such as[untranscribed audio: talkat file ~/.local/share/talkat/untranscribed/20260916_110407_003.wav]. Run that command later to recover the text. Aftermax_consecutive_errors(5) failures in a row, recording stops.
This is the same toggle with a different output: nothing is typed. Each piece is appended to a transcript file as it's recognized, and the whole transcript goes to the clipboard when you stop.
talkat listen --to-file # ~/.local/share/talkat/transcripts/<YYYYMMDD_HHMMSS>_dictation.txt
talkat listen --to-file -o notes.txt # append to a file you chooseWith --postprocess, the cleaned-up text is saved next to the raw file (for
example notes.processed.txt), and that version goes to the clipboard. For
sessions with long silences, raise idle_timeout. max_recording_duration
can go up to an hour.
talkat file meeting.mp3 # print the transcript
talkat file meeting.mp3 -f srt -o meeting.srt # subtitles
talkat file memo.wav -c # also copy it to the clipboard
talkat batch *.wav -o transcripts/ # one output file per inputInput can be wav, mp3, flac or other common audio formats. Output formats
are text (the default), json, srt and vtt. Files go through the
running model server, and uploads larger than max_upload_size_mb (100 MB)
are refused. --language and --postprocess work here too.
talkat model list # downloaded models and their sizes
talkat model download medium.en # download a model
talkat model use medium.en # make it the default (saves model_name)
systemctl --user restart talkat # the server loads the model when it startsThe faster-whisper sizes are tiny, base, small and medium, each
with an English-only .en variant; large-v1, large-v2, large-v3,
large, large-v3-turbo (also turbo); and distil-small.en,
distil-medium.en, distil-large-v2 and distil-large-v3. Larger models
are more accurate but slower, and the .en models only handle English. You
can use a model you haven't downloaded yet: the server fetches it on its
next start. Any HuggingFace repo with a faster-whisper (CTranslate2) model
also works, for example talkat model download org/repo.
Vosk. talkat model only manages faster-whisper models. To use Vosk,
download a model from
alphacephei.com/vosk/models, unpack it
into ~/.cache/talkat/models/vosk/, and set model_name to the unpacked
directory's name:
{ "model_type": "vosk", "model_name": "vosk-model-small-en-us-0.15" }Vosk ignores language and the custom vocabulary,
because the language is part of the model.
GPU. Set "fw_device": "cuda", usually together with
"fw_compute_type": "float16". This needs an NVIDIA GPU and the CUDA
libraries that faster-whisper requires.
The default is en. Change language in your config, or set it for one
run:
talkat listen --language es
talkat file interview.mp3 --language deWith auto, faster-whisper detects the language of each piece.
English-only models (*.en) ignore this setting. For other languages, switch
to a multilingual model, for example talkat model use small together with
"language": "auto".
Put names, jargon and spellings that talkat keeps getting wrong in
~/.config/talkat/dictionary.txt, one per line. They're passed to
faster-whisper as a hint. The server reads this file when it starts, so
restart it after editing: systemctl --user restart talkat.
You can run the transcript through an LLM before it's delivered, to fix grammar, format it as a list, or rewrite it as code. Talkat uses the OpenAI-compatible chat completions API, which Ollama, llama.cpp's server, LM Studio, vLLM, OpenRouter and OpenAI all provide. Define profiles in your config (schema) and pick one per run:
talkat listen --postprocess tidy
talkat listen --to-file --postprocess tidy # applied once, to the whole transcript
talkat file recording.wav --postprocess tidy
talkat batch *.wav -o out/ --postprocess tidyWith --postprocess, the transcript is typed once after processing, not as
you talk. If the LLM is unreachable, returns an error, or times out, you get
the raw transcript and a notification instead. The transcript text goes to
the profile's base_url, so use a local server if it shouldn't leave your
machine.
Settings live in ~/.config/talkat/config.json (under $XDG_CONFIG_HOME if
you set it). An optional /etc/talkat/config.json provides system-wide
defaults. Talkat applies its built-in defaults first, then /etc, then your
file, one key at a time, so your file only needs the settings you change:
{
"silence_threshold": 180.0,
"model_name": "medium.en",
"idle_timeout": 120,
"input_device_name": "headset",
"typing_key_hold_ms": 10
}talkat calibrateandtalkat model usechange only their own setting in your file (silence_thresholdandmodel_name). If the file isn't valid JSON, they leave it untouched and tell you instead.- Command-line flags (
--language,--max-recording,--http-timeout) override the file for one run. - Changes apply to the next
talkatcommand. The exception is settings the model server reads when it starts: everything under Model & recognition exceptlanguage, plusserver_socketandmax_upload_size_mb. After changing those, runsystemctl --user restart talkat.
Validation. Each setting is checked on its own:
- An invalid value, such as the wrong type or out of range, is ignored.
Talkat falls back to the value from
/etc/talkat/config.jsonor the built-in default, and still applies the rest of the file. Only a file that isn't a valid JSON object is ignored entirely. - Unknown keys are flagged, with a suggestion for likely typos
(
idle_timout→idle_timeout). - Numbers must be JSON numbers (
60, not"60"), and switches must betrueorfalse. Settings with whole-number defaults, such as counts and the_mssettings, take whole numbers only. - Paths may start with
~; otherwise they must be absolute. Symlinks are fine.
talkat doctor lists every ignored or unknown setting, and the log records
them too. The old device and model_cache_dir keys no longer exist; use
fw_device, faster_whisper_model_cache_dir and vosk_model_base_dir
instead.
Durations are in seconds unless the name ends in _ms. Ranges are
inclusive.
| Key | Default | Meaning |
|---|---|---|
silence_threshold |
200.0 |
Level that separates speech from pauses. It decides where a recording is cut into pieces, never what's sent. Set by talkat calibrate. 0–10000 |
silence_threshold_fallback |
500.0 |
Threshold calibrate saves if it can't measure the room. 0–10000 |
silence_threshold_min |
50.0 |
Lowest threshold calibrate will save. 0–10000 |
silence_threshold_max |
5000.0 |
Highest threshold calibrate will save. 0–10000 |
input_device_name |
null |
Use the microphone whose name contains this text, ignoring case. null uses the system default input. talkat -v calibrate logs the available device names. Up to 256 characters |
max_recording_duration |
600 |
Hard cap on one recording. 0–3600 |
idle_timeout |
60 |
Stop after this long without transcribed speech. 5–86400 |
idle_notify_interval |
30 |
While it's quiet, remind you this often that recording is still on. 5–3600 |
max_consecutive_errors |
5 |
Stop after this many pieces in a row fail to transcribe (their audio is saved). 1–100 |
audio_normalize_gain |
true |
Even out quiet and loud input before recognition |
audio_target_rms_dbfs |
-20.0 |
Target level for that normalization, in dBFS. −60–0 |
audio_max_gain_db |
20.0 |
Most gain normalization may add, so background noise isn't amplified. 0–60 |
| Key | Default | Meaning |
|---|---|---|
model_type |
"faster-whisper" |
faster-whisper or vosk |
model_name |
"small.en" |
faster-whisper size or HuggingFace repo id, or the Vosk model's directory name. Set by talkat model use |
language |
"en" |
2- or 3-letter ISO 639 code (es, de, yue) or auto. Ignored by .en and Vosk models |
fw_device |
"cpu" |
cpu, cuda or auto |
fw_compute_type |
"int8" |
int8, float16 or float32 |
fw_device_index |
0 |
Which GPU to use with cuda. 0–100 |
faster_whisper_model_cache_dir |
~/.cache/talkat/models/faster-whisper |
Where faster-whisper models are stored |
vosk_model_base_dir |
~/.cache/talkat/models/vosk |
Where Vosk model directories live |
dictionary_file |
~/.config/talkat/dictionary.txt |
Custom vocabulary, one word or phrase per line |
max_segment_seconds |
480 |
Audio longer than this is split at quiet points and transcribed in parts. This mostly affects long files. 5–3600 |
The model server runs sandboxed and can only write under ~/.cache/talkat,
~/.local/share/talkat and ~/.config/talkat. If you move a model
directory elsewhere, allow the new path with systemctl --user edit talkat:
[Service]
ReadWritePaths=/path/to/models| Key | Default | Meaning |
|---|---|---|
output_mode |
"type" |
type types into the focused window. clipboard never types and copies the transcript when you stop |
focus_guard |
true |
Stop typing if the focused window changes (niri, sway, Hyprland) |
typing_key_hold_ms |
5 |
How long each key is held down, in ms (ydotool's --key-hold). Lower is faster; raise it if an app drops characters. 0–100 |
typing_key_delay_ms |
0 |
Pause between keystrokes, in ms. Raise it if an app drops or reorders characters. 0–100 |
save_transcripts |
true |
Save a transcript of each dictation |
transcript_dir |
~/.local/share/talkat/transcripts |
Where transcripts are saved |
postprocess_profiles |
{} |
AI post-processing profiles (schema) |
| Key | Default | Meaning |
|---|---|---|
server_socket |
$XDG_RUNTIME_DIR/talkat/server.sock |
Unix socket that the server and the talkat command share |
http_timeout |
120 |
How long to wait for one transcription request. 0–3600 |
health_check_timeout |
2 |
How long to wait for the server's health check. 0–60 |
file_processing_timeout_base |
30 |
Minimum timeout for talkat file and batch; longer files get twice their duration. 0–3600 |
max_upload_size_mb |
100 |
Largest file talkat file and batch will send. 1–2048 |
| Key | Default | Meaning |
|---|---|---|
process_stop_timeout |
300 |
How long a stopping talkat listen waits for the last pieces to be transcribed and typed before forcing the recording to end. On a forced stop, untyped text goes to the clipboard and untranscribed audio is saved. 0–300 |
lock_acquire_timeout |
1.0 |
How long a command waits for another talkat command to finish. 0–300 |
lock_retry_interval |
0.01 |
Pause between lock attempts. 0–10 |
process_check_interval |
0.1 |
How often a stopping command checks whether the recording has exited. 0–10 |
background_process_delay |
0.5 |
Pause after force-killing a recording that won't exit. 0–60 |
postprocess_profiles maps a profile name to its settings:
{
"postprocess_profiles": {
"tidy": {
"base_url": "http://localhost:11434/v1",
"model": "llama3.2:3b",
"system_prompt": "Clean up grammar and punctuation. Keep the meaning identical. Return only the cleaned text.",
"timeout": 30
},
"openai-clean": {
"base_url": "https://api.openai.com/v1",
"model": "gpt-4o-mini",
"system_prompt": "Format the input as professional prose. Return only the rewritten text.",
"api_key_env": "OPENAI_API_KEY"
}
}
}| Field | Required | Meaning |
|---|---|---|
base_url |
yes | http:// or https:// base URL of an OpenAI-compatible API. Talkat posts to <base_url>/chat/completions |
model |
yes | Model id sent with each request, up to 256 characters |
system_prompt |
yes | Instructions for the rewrite |
api_key_env |
no | Name of the environment variable that holds the API key |
timeout |
no | Seconds to wait for the LLM, more than 0 and up to 600. Default 30 |
Any other field is an error. The API key itself never goes in the config file, only the name of the variable that holds it, so the file is safe to share. That variable must be set where talkat runs. For a hotkey, that's your compositor's environment, not just your shell.
Start with talkat doctor. It checks:
- which talkat you're running, and whether a stale copy shadows it on PATH or in systemd
- the model server, and that the server and the command are the same version
- ydotool, the clipboard and notification tools, and the focus and modifier guards
- your audio devices
- your config files
It exits non-zero if anything failed.
Text lands on the clipboard instead of being typed. The notification says why:
- If ydotool is missing or
ydotooldisn't running, see ydotool setup. To test ydotool on its own, runydotool type helloin a terminal; it should typehelloat your prompt. - If focus moved during dictation, that's the focus guard working.
"focus_guard": falseturns it off. - If a modifier key stayed held, typing pauses while Ctrl, Shift, Alt or Super is down. Check for a stuck key.
Characters go missing or arrive out of order. Some apps can't keep up
with fast typing. Raise typing_key_hold_ms (ydotool's own default is 20)
or typing_key_delay_ms.
The server isn't responding. Check the service, read its log, and restart it:
systemctl --user status talkat
journalctl --user -u talkat -e
systemctl --user restart talkat
curl --unix-socket "$XDG_RUNTIME_DIR/talkat/server.sock" http://talkat/healthThe first start downloads the model, so it can take a while.
Wrong microphone, or no audio. The Audio section of talkat doctor
shows the default input and whether your input_device_name matches a
device. talkat -v calibrate logs every input device name.
Text arrives late in big chunks, or pieces are cut mid-word. Run
talkat calibrate again. A threshold set too low hears room noise as speech,
finds no pauses, and falls back to cutting every 30 seconds. One set too high
mistakes quiet speech for pauses and cuts mid-word.
The hotkey starts and immediately stops. Your binding repeats while the
key is held. Use repeat=false (niri) or --no-repeat (sway).
The toggle gets confused after a crash. If no recording is running
(pgrep -af "talkat listen" shows nothing), delete the stale PID file:
rm "$XDG_RUNTIME_DIR/talkat/listen.pid".
Logs and diagnostics:
- The command and the server both log to
~/.local/share/talkat/logs/talkat.log, which rotates. The server's output is also injournalctl --user -u talkat. - For debug detail in the log, add
-v(talkat -v listen, which works in a hotkey binding too), or setTALKAT_DEBUG=1. - Each dictation writes timings, the model used, and any errors to
~/.local/share/talkat/diagnostics/diagnostics.latest.json. The last 200 records are kept.
CONTRIBUTING.md covers running a checkout without disturbing your installed copy, the checks CI runs, and packaging. Architecture notes and the release process are in CLAUDE.md.