Skip to content

Repository files navigation

            _     _
 __      __| |__ (_)___  _ __ ___
 \ \ /\ / /| '_ \| / __|| '__/ __|
  \ V  V / | | | | \__ \| |  \__ \
   \_/\_/  |_| |_|_|___/|_|  |___/

  speak. type. done.

whisrs

Crates.io docs.rs

Linux-first voice-to-text dictation tool, written in Rust.

Speech-to-text for Wayland, X11, Hyprland, Sway, Niri, GNOME, and KDE. Press a hotkey, speak, and your words appear at the cursor. Works with any app, any window manager, any desktop environment. Supports cloud transcription (Groq, Deepgram, OpenAI) and fully offline local transcription via whisper.cpp. Fast, private, open source. It can also read the selected text back aloud (text-to-speech via Groq, OpenAI, Deepgram, or a local sidecar).


Why whisrs?

Dictation tools like Wispr Flow and Superwhisper are not available on Linux. xhisper proved the concept works, but I kept running into limitations. whisrs takes that idea and rebuilds it in Rust as a single async process with native keyboard layout support, window tracking, and multiple transcription backends.


Installation

Quick install (Linux x86_64 / aarch64)

curl -sSL https://y0sif.github.io/whisrs/install.sh | bash

The install script downloads the latest prebuilt tarball, installs whisrs/whisrsd to /usr/local/bin, and runs interactive setup.

Pin a specific version with WHISRS_VERSION=v0.1.10 or use the cloud-only minimal build with WHISRS_MINIMAL=1. Re-run the same command later to upgrade.

To build from source instead — including custom feature flag combos or unsupported architectures — use cargo install whisrs --locked or the whisrs-git AUR package.

After install, press your hotkey to start recording, press again to stop. Text appears at your cursor.

GPU acceleration (local whisper.cpp)

The default build — and every prebuilt tarball that ships whisper.cpp at all — runs it on the CPU. If you use the local-whisper backend, building with a GPU feature moves the model onto your GPU and cuts dictation latency from seconds to near-instant:

cargo install whisrs --features vulkan
Feature Backend Hardware
vulkan Vulkan AMD, Intel, NVIDIA (cross-vendor; the safe default)
cuda CUDA NVIDIA, needs the CUDA toolkit
hipblas ROCm/HIP AMD, needs ROCm

These are compile-time features — the GPU backend has to be linked in, so there is no runtime switch. Each one implies local-whisper; the cloud backends are unaffected, and CPU stays the default.

Build-time system dependencies (on top of the usual alsa-lib, libxkbcommon, clang, cmake). For vulkan:

# Arch Linux
sudo pacman -S vulkan-headers vulkan-icd-loader shaderc

# Debian/Ubuntu
sudo apt install libvulkan-dev glslc

# Fedora
sudo dnf install vulkan-headers vulkan-loader-devel glslc

Your GPU driver package alone is not enough. The driver ships the runtime, not the development headers or the shader compiler, so a machine that runs Vulkan games fine will still fail the build with:

Could NOT find Vulkan (missing: Vulkan_INCLUDE_DIR)

Install the packages above and rebuild. cuda and hipblas likewise need their full toolkits (cuda / rocm-hip-sdk), not just the driver.

Verify it worked. The binary should link against the Vulkan loader:

ldd ~/.cargo/bin/whisrsd | grep vulkan

No output means you got a CPU build. Then start the daemon in the foreground and watch whisper.cpp report the device it picked up:

RUST_LOG=debug whisrsd

A working Vulkan build names your GPU at load time (for example ggml_vulkan: Found 1 Vulkan devices: Radeon RX 9070 XT (RADV GFX1201)) and loads the model onto it.

If you installed whisrs from a distro package or the tarball, cargo install writes the new binaries to ~/.cargo/bin and leaves the old ones in /usr/local/bin or /usr/bin untouched. Check that your systemd unit still points at the binary you just built — systemctl --user show whisrs.service -p ExecStart — and point ExecStart at ~/.cargo/bin/whisrsd if it doesn't, otherwise you'll keep running the CPU build without noticing. Don't just delete the old binary: whisrs setup writes an absolute ExecStart, so removing what it points at stops the daemon starting rather than moving it to the new build.

Other install methods (pre-built binary, AUR, Cargo, Nix, manual)

Pre-built binary (manual)

The Quick install above already does this — this section is for users who want to install the tarball by hand.

Each tagged release publishes tarballs on GitHub Releases with both whisrs and whisrsd plus the contrib files (udev rule, systemd unit, man pages).

# Pick the artifact for your arch + variant:
ARCH=x86_64   # or aarch64
curl -sSL -o whisrs.tar.gz https://github.com/y0sif/whisrs/releases/latest/download/whisrs-linux-${ARCH}.tar.gz

# Or the minimal build (cloud backends only — no whisper.cpp; keeps tray + overlay):
# curl -sSL -o whisrs.tar.gz https://github.com/y0sif/whisrs/releases/latest/download/whisrs-linux-${ARCH}-minimal.tar.gz

tar xzf whisrs.tar.gz
sudo install -m755 whisrs whisrsd /usr/local/bin/
sudo install -m644 contrib/99-whisrs.rules /etc/udev/rules.d/
# On NixOS/Guix, point the rule's ACL fallback at your setfacl:
# command -v setfacl >/dev/null && sudo sed -i "s|/usr/bin/setfacl|$(command -v setfacl)|g" /etc/udev/rules.d/99-whisrs.rules
sudo udevadm control --reload-rules && sudo udevadm trigger
sudo usermod -aG input $USER   # log out / back in for the group change
whisrs setup
Variant Architectures Includes local whisper.cpp
whisrs-linux-{x86_64,aarch64}.tar.gz x86_64, aarch64 yes (full build)
whisrs-linux-{x86_64,aarch64}-minimal.tar.gz x86_64, aarch64 no (cloud backends only)

Arch Linux (AUR)

yay -S whisrs-git

After install, run whisrs setup to configure your backend, API keys, permissions, and keybindings.

Cargo

cargo install whisrs

Requires system dependencies: alsa-lib, libxkbcommon, clang, cmake.

After install, run whisrs setup.

Nix

nix profile install github:y0sif/whisrs

Or add to your flake inputs:

inputs.whisrs.url = "github:y0sif/whisrs";

Manual install

1. Dependencies

# Arch Linux
sudo pacman -S base-devel alsa-lib libxkbcommon clang cmake

# Debian/Ubuntu
sudo apt install build-essential libasound2-dev libxkbcommon-dev libclang-dev cmake

# Fedora
sudo dnf install gcc-c++ alsa-lib-devel libxkbcommon-devel clang-devel cmake

2. Build

git clone https://github.com/y0sif/whisrs
cd whisrs
cargo install --path .

3. Setup

whisrs setup

The interactive setup will walk you through backend selection, API keys / model download, microphone test, uinput permissions, systemd service, and keybindings.

4. Bind a hotkey

Example for Hyprland (~/.config/hypr/hyprland.conf):

bind = $mainMod, W, exec, whisrs toggle

Example for Sway (~/.config/sway/config):

bindsym $mod+w exec whisrs toggle

Transcription Backends

Backend Type Streaming Cost Best for
Groq Cloud Batch Free tier available Getting started, budget use
Deepgram Streaming Cloud (WebSocket) True streaming $200 free credit Streaming with free credits
Deepgram REST Cloud Batch $200 free credit Simple, 60+ languages
OpenAI Realtime Cloud (WebSocket) True streaming Paid Best UX, text as you speak
OpenAI REST Cloud Batch Paid Simple fallback
OpenAI-compatible Realtime External WebSocket Completed-utterance realtime Free / self-hosted Lemonade and similar OpenAI-style ASR servers
Local whisper.cpp Local (CPU/GPU) Silence-split phrases Free Privacy, offline use
ASR sidecar Local sidecar Batch Free Bring-your-own local ASR (Moonshine, Parakeet, VibeVoice-ASR, …)

Groq is the default. For fully offline use, run whisrs setup and select Local > whisper.cppbase.en (142 MB, ~388 MB RAM) is recommended; tiny.en (75 MB) for low-end hardware, small.en (466 MB) for higher accuracy.

Local whisper.cpp streams by splitting dictation into phrases at natural pauses and decoding each phrase exactly once (the [local-whisper] defaults segmentation = "silence", phrase_silence_ms = 400); set segmentation = "window" for the legacy overlapping sliding window. See docs/configuration.md.

For local ASR models without a Rust runtime (Moonshine, NVIDIA Parakeet, Microsoft VibeVoice-ASR), use the generic ASR sidecar backend — it talks to a small local HTTP service that hosts the model. See contrib/asr-sidecars/ for ready-to-run sidecars.

For external realtime servers that speak the OpenAI Realtime transcription event model over WebSocket, use backend = "openai-compatible-realtime". Lemonade is the first supported profile. Unlike OpenAI cloud, Lemonade-style interim partials are replaceable, so whisrs types completed phrases as they stabilize instead of blindly appending every partial hypothesis.


Configuration

Config file: ~/.config/whisrs/config.tomlwhisrs setup writes a working file. A minimal example:

[general]
backend = "groq"   # groq | deepgram-streaming | deepgram | openai-realtime | openai | openai-compatible-realtime | local-whisper | asr-sidecar
language = "en"    # ISO 639-1 or "auto"
overlay = false    # bottom-screen recording overlay

[groq]
api_key = "gsk_..."

Env-var overrides: WHISRS_GROQ_API_KEY, WHISRS_DEEPGRAM_API_KEY, WHISRS_OPENAI_API_KEY.

For the full reference (overlay, [input], [openai-compatible-realtime], [asr-sidecar], [llm], [hotkeys], [hooks], GNOME extension setup), see docs/configuration.md.


CLI Commands

whisrs setup     # Interactive onboarding
whisrs config    # Interactive editor for ~/.config/whisrs/config.toml
whisrs toggle    # Start/stop recording (uses general.language)
whisrs toggle -l en  # Start/stop recording, overriding the language for this session
whisrs cancel    # Cancel recording, discard audio
whisrs status    # Query daemon state
whisrs restart   # Restart the daemon (uses the systemd user service when present)
whisrs command   # Command mode: select text + speak instruction → LLM rewrite
whisrs llm-command <name>      # Toggle a named [[llm_commands]] entry (see config.toml)
whisrs llm-command-set <name>  # Reprogram a named LLM command from the current selection
whisrs speak     # Read the selected text aloud (alias: whisrs read; press again to stop)
whisrs log       # Show recent transcription history
whisrs log -n 5  # Show last 5 entries
whisrs log --clear  # Clear all history

Per-language keys

toggle accepts an optional --language/-l <CODE> (ISO 639-1, or auto) that overrides general.language for that one session only -- no config edit or daemon restart. Bind a separate key per language so you can dictate in each without switching settings. Hyprland:

bind = , F1, exec, whisrs toggle -l en
bind = , F2, exec, whisrs toggle -l pl

Sway:

bindsym F1 exec whisrs toggle -l en
bindsym F2 exec whisrs toggle -l pl

Without -l, whisrs toggle keeps using general.language, so an existing plain-toggle key is unaffected.

LLM post-processing

Three ways to put an LLM between your voice and the cursor, all sharing the one [llm] section:

  • whisrs command: select text, speak an instruction, the selection is rewritten in place.
  • [[llm_commands]]: a named instruction on its own hotkey. Dictate, the LLM applies it, the result is typed. One hotkey per entry.
  • [general] llm_post_process: the same rewrite on the normal toggle key, with no extra binding.
[general]
backend = "groq"           # a batch backend
llm_post_process = true    # off by default
llm_instruction = "Fix punctuation and obvious transcription errors. Keep the wording unchanged. Return only the corrected text."

With it on, every whisrs toggle dictation goes through [llm] before it is typed.

Batch backends only: deepgram, groq, openai, asr-sidecar. The streaming backends, which are deepgram-streaming, openai-realtime, openai-compatible-realtime and local-whisper, type text at the cursor as it arrives, so no whole transcript ever exists to post-process and the flag does nothing at all. local-whisper is the one to watch: it runs offline and transcribes in a single call, but dictation with it always streams, so llm_post_process is a silent no-op there too. whisrsd warns at startup if you pair the two. Use an [[llm_commands]] hotkey instead, which works whatever the backend.

If the LLM call fails, times out, or returns nothing, the raw transcript is typed instead, so a dictation is never lost to post-processing. A post-processed entry shows up in whisrs log tagged <backend>+llm (for example groq+llm); a dictation that fell back to the raw transcript keeps the plain backend name. See docs/configuration.md.


Supported Environments

Component Support
Hyprland Tested by maintainer and community (Arch Linux)
Sway / i3 Implemented; additional reports welcome
Niri Implemented; tested by contributor on Niri 26.04 (CachyOS)
X11 (any WM) Tested by community on Ubuntu 24.04 (Xorg)
GNOME Wayland Tested by community on Ubuntu 24.04 and Arch (mutter); overlay via the bundled GNOME Shell extension
KDE Wayland Implemented via D-Bus; reports welcome
Audio PipeWire, PulseAudio, ALSA (auto-detected via cpal)
Distros Confirmed on Arch Linux and Ubuntu 24.04; any Linux with the system dependencies above

Note: whisrs is daily-driven on Hyprland (Arch Linux), with community confirmation on GNOME Wayland (Ubuntu 24.04 + Arch), Xorg (Ubuntu 24.04), and Niri (CachyOS). Sway, i3, and KDE reports are still wanted — if you use whisrs there, please open an issue with what works and what doesn't.


Project Status

whisrs is functional and usable for daily dictation. Streaming transcription, command mode, read-selection-aloud (TTS via Groq, OpenAI, Deepgram, or a local sidecar), multi-language support, system tray (status, restart and quit in the menu; left-click toggles recording where the tray host forwards it, as waybar and Plasma do), OSD overlay, layout-aware injection (incl. AltGr + dead keys), the generic ASR sidecar backend (Moonshine, Parakeet, VibeVoice-ASR), and packaging for AUR / Nix / crates.io all ship today. Native local Vosk and Parakeet backends are next.

Per-release details: docs/version-roadmap.md.


Troubleshooting

See docs/troubleshooting.md for the full list. Two issues come up often enough to call out here:

Garbled output / wrong characters on non-US layouts

whisrs auto-detects your XKB layout via the active compositor (Hyprland / Sway), then setxkbmap (X11), then localectl (systemd), then the XKB_DEFAULT_LAYOUT / XKB_DEFAULT_VARIANT env vars — in that order. If none succeed, it falls back to US/QWERTY, and on a non-US layout that produces garbled output (e.g. "this" typed as "èCDU" on fr(bepo)).

To diagnose, run the daemon in the foreground with debug logging and look for the detected layout:

RUST_LOG=debug whisrsd

If the layout is missing or wrong, fix it one of two ways:

  1. Make sure localectl status reports the right X11 Layout and X11 Variant. This is the system source-of-truth and works without any X session env vars.

  2. Force the layout via env vars in your systemd service override:

    systemctl --user edit whisrs.service
    [Service]
    Environment=XKB_DEFAULT_LAYOUT=fr
    Environment=XKB_DEFAULT_VARIANT=bepo

    Then systemctl --user restart whisrs.service.

Hotkey keys are physical positions, not layout characters

The configured hotkey trigger (e.g. Ctrl+Shift+W) is interpreted as the physical evdev keycode at the US/QWERTY W position, regardless of the active layout. This is intentional — the hotkey listener reads raw evdev events before any XKB translation, which is how every evdev-based hotkey tool works (xremap, sxhkd --evdev). On non-US layouts, pick the trigger by its physical position on a QWERTY keyboard.


Contributing

The biggest way to help right now:

  1. Test on your compositor — Sway, i3, KDE, GNOME. Report what works and what doesn't.
  2. Test on your distro — Ubuntu, Fedora, NixOS, etc. Build issues, missing deps, etc.
  3. Bug reports — if text goes to the wrong window, characters get dropped, or audio doesn't capture, open an issue.

See CONTRIBUTING.md for development setup and project structure.



License

MIT

About

Linux-first voice-to-text dictation tool for Wayland, X11, Niri, Hyprland & Sway, written in Rust

Topics

Resources

Contributing

Stars

87 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages