TuxSpeak: Linux Dictation That Types at Your Cursor
Linux dictation that types at your cursor. How TuxSpeak pairs Groq Whisper with Gemini polishing to put clean text into any app, with no cloud dashboard.

TL:DR: Linux dictation has been the weak spot of the desktop for years. TuxSpeak fixes it with a small shell pipeline: press Alt+Space, speak, press it again. Groq Whisper transcribes, Gemini fixes the obvious errors, and a virtual keyboard types the result wherever your cursor already sits. GPL 3.0, two free API keys, no cloud dashboard.
Dictation on macOS and Windows is a solved problem. You press a key, you talk, text appears. On Linux it has been a decade of half-answers: browser tabs you have to copy out of, tools that only work in one editor, or local models that eat your GPU and still mangle proper nouns.
I wanted the boring version. Press a key anywhere, talk, get clean text at the cursor. No window to switch to, no paste step, no subscription. That is what TuxSpeak does, and this is how it works.
What "linux dictation" actually has to solve
The hard part is not transcription. Speech-to-text got good years ago. The hard part is the last two inches: getting the text into whatever window you are actually typing in.
A dictation tool on Linux has to answer four questions:
- How does it hear you, given PulseAudio, PipeWire, ALSA, and JACK all exist?
- How does it turn audio into text without a 3GB model download?
- How does it clean up speech, because nobody speaks in clean prose?
- How does it type into an arbitrary window, across X11 and Wayland, without being a browser extension?
Most tools answer two or three of these and give up on the fourth. The fourth is the one that decides whether you actually use the thing.
| Approach | Works in any app | Local audio | Setup cost |
|---|---|---|---|
| Browser-based dictation | No, copy and paste | No | Low |
| Editor or IDE plugin | No, one app only | Depends | Low |
| Local Whisper model | Yes, with glue code | Yes | High, GPU and model weights |
| TuxSpeak | Yes | Yes, audio stays local | Medium, one script |
How TuxSpeak works
The whole thing is a bash script and a small C binary. There is no Electron app and no tray icon.
Press Alt+Space. The script checks whether a recording is already running. If not, it types (recording...) at your cursor and starts pw-record at 16kHz mono. That status text is a real hint, not a notification: you can see the tool is live, in the window you are already looking at.
Speak. Nothing happens remotely yet. The audio is a WAV file in /tmp.
Press Alt+Space again. The recording stops. The script deletes the (recording...) text it typed, then runs the pipeline:
- A silence check via
ffmpegvolumedetect. If the loudest peak is below the threshold, it types(no sound detected), erases it, and exits. No API call, no wasted quota. - The WAV goes to Groq for transcription with Whisper. Short clips use
whisper-large-v3-turbo; longer recordings switch towhisper-large-v3. - The raw transcript goes to Gemini 3.1 Flash Lite for cleanup.
- The result is typed at your cursor.
Each step swaps its own status text in place: (recording...) becomes (transcribing...) becomes (polishing...) becomes your sentence. The script types a status, deletes exactly as many characters as it typed, then types the next one. It is a crude trick and it works in every text field on the system, which is the point.
The polishing prompt is deliberately narrow
This is the part I spent the most time on, and the part most likely to go wrong.
The temptation with an LLM in the loop is to let it improve your writing. That is a trap. If dictation rewrites your sentences, you stop trusting it, and you end up reading every output carefully instead of just talking. The tool becomes slower than typing.
So the prompt tells Gemini to fix obvious spelling, grammar, and transcription errors, and explicitly not to rephrase, restructure sentences, or change tone. Preserve the original wording. Only fix what is clearly wrong.
In practice that turns this:
um so basically the the deploy script needs to check for uh the env file before it runs right so we dont crash on missing keys
into this:
So the deploy script needs to check for the env file before it runs, so we don't crash on missing keys.
The filler is gone, the contraction is fixed, the meaning is untouched. If polishing fails or returns nothing, the script falls back to the raw transcript rather than typing nothing. A failed API call costs you a slightly messier sentence, not a lost thought.
The part nobody else solves: typing anywhere
TuxSpeak is built on xhisper, an MIT-licensed C tool that creates a virtual keyboard through the Linux uinput subsystem. That choice is what makes the whole thing work across the desktop.
Because uinput operates at the kernel input layer, the characters TuxSpeak produces are indistinguishable from a real keyboard. There is no compositor integration, no accessibility API, no per-app support matrix. The kernel documents uinput as a generic input device interface, and every application on the machine already knows how to read a keyboard.
The practical consequence is that dictation works in Firefox, in a terminal, in VS Code, in a Slack message box, and in a GTK dialog, without any of them knowing TuxSpeak exists. It also means it works the same on X11 and Wayland, which is not true of the xdotool approach that many older tools use.
There is one wrinkle. ASCII characters get typed as key events. Anything else, accented characters, em dashes, emoji, goes through the clipboard and a simulated paste, because mapping arbitrary Unicode to keycodes is not worth the complexity. You will not notice unless you dictate a lot of non-English text.
This is the same instinct behind everything else we build: pick the layer that already works instead of adding one. It is the argument in the zen of a simple tech stack, applied to input devices.
What it costs to run
Nothing, for realistic usage.
Both APIs have free tiers that are wide enough for daily dictation. Groq's free tier covers Whisper transcription. Gemini 3.1 Flash Lite is one of the cheapest models available and the polish request is tiny, a few hundred tokens per dictation at most.
The audio stays on your machine except for the transcription call itself. There is no account to make with us, no telemetry, and no dashboard. If Groq or Google ever become a problem, both calls are plain curl in a shell script that you can point somewhere else in about five minutes.
That is a deliberate design property, not an accident. Owning the pipeline is the same reason we argue for owning your data rather than renting it.
Installing it
git clone https://github.com/anirudhprashant/tuxspeak.git
cd tuxspeak
./install-xhisper.sh
The installer does the tedious parts: system dependencies, prompting for your two API keys and writing them to ~/.env, compiling the C tools, setting a udev rule so /dev/uinput is usable, and adding you to the input group.
Two things to know. First, reboot after install if it added you to the input group, because group membership does not apply to your existing session. Second, you have to bind the shortcut yourself, since GNOME uses Alt+Space for the window menu by default:
gsettings set org.gnome.desktop.wm.keybindings activate-window-menu "[]"
Then create a custom shortcut pointing at xhisper-gemini. KDE, i3, Sway, and Hyprland all work; the binding step just moves to that environment's config.
TuxSpeak does run one small background daemon, xhispertoold, which holds the virtual keyboard device open. The installer starts it and adds it to autostart. It is not a cloud agent, but it is honest to say it exists rather than claim the tool is daemon-free.
History, because dictation gets lost
Every dictation is written to ~/.local/share/xhisper/history.txt with a timestamp, the raw transcript, and the polished version.
This started as a debugging feature and became the thing I use most. When Whisper mishears a proper noun, the raw transcript tells you whether transcription or polishing was at fault. When you dictate something good into a text box that then eats it, the history has it.
There is a small GUI popup (xhisper-gui) that shows your last dictation with Copy and History buttons, plus xhisper-last and xhisper-history if you would rather stay in the terminal.
Where it falls short
Being straight about the limits:
- It needs an internet connection. Both the transcription and the polish are API calls. A fully local Whisper build is possible and is not what this tool is.
- It requires PipeWire. Recording uses
pw-record. If you are on pure PulseAudio, you will need to change one line. - The GUI popup coordinates are tuned for 1920x1080. They are two variables at the top of the script, but they are hardcoded.
- The polish step adds latency. It is roughly a second for a normal sentence. If you want raw speed and do not mind filler words, you can point the shortcut at the raw path.
- It is English-first. Whisper handles other languages, but the polish prompt and the non-ASCII clipboard path have only been exercised in English.
FAQ
Does TuxSpeak work on Wayland?
Yes. Because it types through the kernel uinput layer rather than through X11 automation like xdotool, it does not care which display server you use. The only Wayland-specific wrinkle is the GUI popup, which forces the X11 GTK backend so that window positioning works.
Is my audio sent to the cloud?
The audio file is sent to Groq for transcription, and the resulting text is sent to Gemini for polishing. Nothing else leaves the machine, there is no analytics, and no audio is retained locally beyond the temporary WAV, which is deleted after each dictation.
Do I need a GPU?
No. That is the main reason this design uses hosted Whisper rather than a local model. A local Whisper large model wants several gigabytes of VRAM to be responsive. TuxSpeak runs fine on a laptop with integrated graphics because the transcription happens on Groq's hardware.
What does it actually cost per month?
For normal individual use, nothing. Both Groq and Gemini have free tiers with no credit card required, and a dictation is a few seconds of audio plus a few hundred tokens. Heavy all-day usage could eventually cross a free tier limit, at which point the cost is still measured in cents.
How is this different from a local Whisper setup?
A local setup gives you full offline capability and costs you GPU memory, model downloads, and slower transcription on modest hardware. TuxSpeak trades offline capability for speed and near-zero resource usage. If you need to dictate on a plane, build the local version. If you want it to be instant on a thin laptop, use this.
Why is it GPL when xhisper is MIT?
The xhisper C core is MIT and stays MIT, with credit to its author. The TuxSpeak wrapper, scripts, and installer are GPL 3.0, which means modifications and forks of that layer stay open too. It is the same reasoning behind our other open source work.
Bottom line
Linux dictation was never blocked on speech recognition. It was blocked on the unglamorous part: getting text into an arbitrary window without asking every application to cooperate. A kernel-level virtual keyboard solves that, and once it is solved, the rest is a hundred lines of shell.
TuxSpeak is roughly the smallest thing that could work. There is no daemon doing clever background inference, no model to manage, no account. Two API keys, one script, and a keyboard shortcut. If you want the same argument applied to the rest of your tooling, the BareStack features page and our comparison writeups cover it, and the FAQ answers the usual objections.
It is on GitHub under GPL 3.0. If it mangles your dictation in an interesting way, open an issue and paste the history entry, since it has both the raw and polished versions and that is usually enough to find the cause.
If you like the idea of tools that do one job without renting you the privilege, the same thinking runs through our take on self-hosting and the open source alternatives we keep recommending.
Anirudh Prashant · Founder & Lead Engineer, BareStack
Founder of BareStack. Builds custom, no-bloat software, self-hosted tooling, and AI automations for solopreneurs and small teams.