MCPcopy Index your code
hub / github.com/better-slop/hyprwhspr-rs

github.com/better-slop/hyprwhspr-rs @v0.3.32

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.3.32 ↗ · + Follow
747 symbols 1,564 edges 57 files 20 documented · 3%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

hyprwhspr-rs logo

hyprwhspr-rs

Rust implementation of hyprwhspr | Blazing fast, native speech-to-text voice dictation for Hyprland and Omarchy | Waybar, Walker, and Elephant integrations (optional)

cargo install hyprwhspr-rs

GitHub Release crates downloads license


https://github.com/user-attachments/assets/bbbaa1c3-1a7e-4165-ad3d-27b7465e201a

Requirements

  • whisper.cpp (GitHub, AUR)
  • Ensure whisper-cli is available on your PATH (or use the managed build locations).
  • libudev + pkg-config (required for hotplug detection; libudev-dev on Debian/Ubuntu)
  • GNU-only binaries (no musl releases)
  • Groq or Gemini API key (optional)
  • Use GROQ_API_KEY for provider groq
  • Use GEMINI_API_KEY for provider gemini
  • Groq with whisper is cheap (~$0.10 USD/month) and fast as hell. [Data Controls]
  • Comparatively, Gemini is very slow but offers better output formatting.
  • ChatGPT account via Codex auth (optional)
  • Use custom.openai-codex with ~/.codex/auth.json
  • Parakeet TDT (optional) - NVIDIA's local ASR model via ONNX
  • Run ./scripts/download-parakeet-tdt.sh to download model files (~1.2GB)
  • Very fast, but not as accurate as whisper or Gemini
  • For custom OpenAI-compatible providers, see configuration below

Features

  • Fast speech-to-text
  • Intuitive configuration
  • word overrides use a bit of regex and inverse text normalization (ITN) by FluidInference (see below)
  • multi provider support (OpenAI, local whisper, parakeet) - websocket support coming soon!
  • hot reloading during runtime
  • Optional fast VAD trims (fast_vad.enabled) audio files, reducing inferences costs while increasing output speed
┌─ Text Pipeline (steps: 11, changed: 4)
│ IN  : Yep. Tab, newline, test, newline, newline, test, open parenthesis, hello world, close parenthesis, period
│ • inverse_text_normalization (applied ×62)
│   - Yep. Tab, newline, test, newline, newline, test, open parenthesis, hello world, close parenthesis, period
│   + Yep. Tab, newline, test, newline, newline, test, (, hello world, ), .
│ • control_commands (applied ×4)
│   - Yep. Tab, newline, test, newline, newline, test, (, hello world, ), .
│   + Yep. ⇥, ⏎, test, ⏎, ⏎, test, (, hello world, ), .
│ • control_artifact_cleanup (applied)
│   - Yep. ⇥, ⏎, test, ⏎, ⏎, test, (, hello world, ), .
│   + Yep.⏎test,⏎⏎test, (hello world).
│ • capitalize_after_period (applied ×2)
│   - Yep.⏎test,⏎⏎test, (hello world).
│   + Yep.⏎Test,⏎⏎Test, (hello world).
│ OUT : Yep.⏎Test,⏎⏎Test, (hello world).
└─

Built for Hyprland

  • Detects Hyprland via HYPRLAND_INSTANCE_SIGNATURE and opens the IPC socket at $XDG_RUNTIME_DIR/hypr/<signature>/.socket.sock.
  • Execs dispatch sendshortcut commands against the active window to paste dictated text, inspecting activewindow to decide when Shift is required for a hardcoded list of programs.
  • Falls back to a Wayland virtual keyboard client or a simulated keypress paste if IPC communication fails.
  • Supports daemon control commands via hyprwhspr-rs record {start|stop|toggle|status} so Hyprland can own shortcut capture with bind / bindr.
  • See the example docs for additional integration paths outside of Waybar and Walker/Elephant.

Hyprland capture-first binds

The Linux Kernel treats input grabbing as an exclusive operation. If hyprwhspr-rs were to grab a keyboard device directly with EVIOCGRAB, it would become the sole recipient of that device's events until the grab was released. That is the wrong layer for push-to-talk dictation because it would require re-injecting every non-shortcut keypress through a virtual keyboard just to preserve normal typing.

For Hyprland, the cleaner approach is to let the compositor own shortcut capture and have hyprwhspr-rs expose recorder controls over a local socket. In practice, that means keeping the daemon running in the background and binding record start / record stop / record toggle directly in Hyprland:

bind = ALT, grave, exec, hyprwhspr-rs record start
bindr = ALT, grave, exec, hyprwhspr-rs record stop
bind = ALT, SPACE, exec, hyprwhspr-rs record toggle

Installation

From crates.io

  1. Install the latest release from crates.io

bash cargo install hyprwhspr-rs

Omit parakeet backend:

bash cargo install hyprwhspr-rs --no-default-features

  1. Install systemd service and Waybar module (optionally, with a WIP elephant/walker menu using --with-elephant flag)

```bash # Interactive install hyprwhspr-rs install

# Optionally, install specific components (systemd, waybar, elephant) hyprwhspr-rs install {--all| --service | --waybar | --elephant} {--force | -f} ```

Notes:

  • The installer writes the systemd unit with an absolute ExecStart= pointing at the hyprwhspr-rs binary you ran hyprwhspr-rs install with. If you copy the unit template manually, ensure hyprwhspr-rs is resolvable by systemd (PATH / drop-in override).
  • If audio start/stop sounds are missing in your packaging setup, you can point the app at an installed assets directory with HYPRWHSPR_ASSETS_DIR=/path/to/assets.

Using Nix

You can install the hyprwhspr-rs package from nixpkgs.

With NixOS:

{
  # required to listen for keyboard shortcuts
  users.users.<username>.extraGroups = [ "input" ];

  # have it auto start as a systemd unit with
  services.hyprwhspr-rs.enable = true;
  # or just add it to your systemPackages
  environment.systemPackages = [ pkgs.hyprwhspr-rs ];

  # optional: to enable cuda (for AMD do `rocmSupport` instead of `cudaSupport`)
  # cuda is unfree so not in the default nixos build caches
  # I highly recommend adding the cuda build cache to your nixconfig https://discourse.nixos.org/t/cuda-cache-for-nix-community/56038
  services.hyprwhspr-rs = {
    enable = true;
    package = pkgs.hyprwhspr-rs.override {
      # to optimize build time you can skip enabling cudaSupport for one of these two
      # for whisper do whisper-cpp, for NVIDIA Parakeet do onnxruntime
      whispercpp = pkgs.whisper-cpp.override { cudaSupport = true; };
      onnxruntime = pkgs.onnxruntime.override { cudaSupport = true; };
    };
  };
  # you can also enable cuda/rocm globally, but this will increase the build time for your entire system if you dont add the cuda build cache
  nixpkgs.config.cudaSupport = true;

  # if you use groq or gemini for transcription, you can autoload their keys with
  services.hyprwhspr-rs = {
    enable = true;
    # put `GROQ_API_KEY=...` or `GEMINI_API_KEY=...` in the file you put at this path
    environmentFile = "/path/to/hyprwhspr_secret_file";
  };
}

From source

  1. git clone https://github.com/better-slop/hyprwhispr-rs.git
  2. cd hyprwhspr-rs
  3. cargo build --release
  4. sudo cp target/release/hyprwhspr-rs /usr/local/bin/

Waybar Integration

./scripts/install-waybar.sh

Configuration

    <strong>Example hyprland bindings config</strong>

Configure in, e.g., ~/.config/hypr/hyprland.conf

# hold
bind = ALT, GRAVE, exec, hyprwhspr-rs record start
bindr = ALT, GRAVE, exec, hyprwhspr-rs record stop

# tap
bind = ALT, SPACE, exec, hyprwhspr-rs record toggle
<strong>Example hyprwhspr-rs config</strong>

Configure in ~/.config/hyprwhspr-rs/config.jsonc

Starting with v0.28.0, you may use "$schema": "https://raw.githubusercontent.com/better-slop/hyprwhspr-rs/<vX.X.X|main>/config/schema.json" to validate your config.

```jsonc { "$schema": "https://raw.githubusercontent.com/better-slop/hyprwhspr-rs/main/config/schema.json", "shortcuts": { "press": "SUPER+ALT+D", "hold": "SUPER+ALT+CTRL", }, "word_overrides": { "under score": "_", "em dash": "—", "equal": "=", "at sign": "@", "pound": "#", "hashtag": "#", "hash tag": "#", "newline": "\n", "Omarkey": "Omarchy", "dot": ".", "Hyperland": "hyprland", "hyperland": "hyprland", }, "audio_feedback": true, // Play start/stop sounds while recording "start_sound_volume": 0.1, // 0.1 - 1.0 "stop_sound_volume": 0.1, // 0.1 - 1.0 "start_sound_path": null, // Optional custom audio asset overrides "stop_sound_path": null, // Optional custom audio asset overrides "auto_copy_clipboard": true, // Automatically copy the final transcription to the clipboard "shift_paste": false, // Whether to force shift paste "global_paste_shortcut": false, // Enable compositor-level paste; uses Hyprland sendshortcut with Shift+Insert for all pastes "paste_hints": { "shift": [ // List of window classes that will always paste with Ctrl+Shift+V ], "shift_insert": [ // List of window classes that will always paste with Shift+Insert ], }, "audio_device": null, // Force a specific input device index (null uses system default) "fast_vad": { "enabled": false, // Enable Earshot fast VAD trimming "profile": "aggressive", // quality | low_bitrate | aggressive | very_aggressive (lowercase only, serde-enforced; default aggressive) "min_speech_ms": 120, // Minimum detected speech before keeping a segment "silence_timeout_ms": 500, // Drop silence longer than this (ms) "pre_roll_ms": 120, // Audio to keep before speech to avoid clipping words "post_roll_ms": 150, // Audio to keep after speech before trimming "volatility_window": 24, // Frames observed for adaptive aggressiveness (30 ms per frame, matches FRAME_MS in src/audio/vad.rs) "volatility_increase_threshold": 0.35, // Bump profile when toggles exceed this ratio "volatility_decrease_threshold": 0.12, // Relax profile when toggles stay below this ratio }, "transcription": { "provider": "whisper_cpp", // whisper_cpp | groq | gemini | parakeet | custom. "request_timeout_secs": 45, "max_retries": 2, "whisper_cpp": { "prompt": "Transcribe as technical documentation with proper capitalization, acronyms, and technical terminology. Do not add punctuation.", "model": "large-v3-turbo-q8_0", // Whisper model to use (must exist in specified directories) "threads": 4, // CPU threads dedicated to whisper.cpp "gpu_layers": 999, // Number of layers to keep on GPU (999 = auto/GPU preferred) "fallback_cli": false, // Fallback to whisper-cli (uses CPU) "no_speech_threshold": 0.6, // Whisper's "no speech" confidence gate "models_dirs": ["~/.local/share/hyprwhspr-rs/models"], // Directories to search for models "vad": { "enabled": false, // Toggle whisper-cli's native Silero VAD "model": "ggml-silero-v5.1.2.bin", // Path or filename for the ggml Silero VAD model // Probability threshold for deciding a frame is speech. Higher = fewer false positives, but may miss quiet speech. "threshold": 0.5, // Minimum contiguous speech duration (ms) to accept. Increase to ignore quick clicks/taps. "min_speech_ms": 250, // Minimum silence gap (ms) required to end a speech segment. Raise if mid-sentence pauses are being split. "min_silence_ms": 120, // Maximum speech duration (seconds) before forcing a cut. Use null (or omit) to leave unlimited. "max_speech_s": 15.0, // Extra padding (ms) added before/after detected speech so words aren't clipped. "speech_pad_ms": 80, // Overlap ratio between segments. Higher overlap helps smooth transitions at the cost of a little extra decode time. "samples_overlap": 0.1, }, }, "groq": { "model": "whisper-large-v3-turbo", "endpoint": "https://api.groq.com/openai/v1/audio/transcriptions", "prompt": "Transcribe as technical documentation with proper capitalization, acronyms, and technical terminology. Do not add punctuation.", }, "gemini": { "model": "gemini-2.5-flash-preview-09-2025", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models", "temperature": 0.0, "max_output_tokens": 1024, "prompt": "Transcribe as technical documentation with proper capitalization, acronyms, and technical terminology. Do not add punctuation.", }, "parakeet": { "model_dir": "models/parakeet/parakeet-tdt-0.6b-v3-onnx", // Relative to $XDG_DATA_HOME/hyprwhspr-rs (or ~/.local/share/hyprwhspr-rs) "prompt": "Transcribe as technical documentation with proper capitalization, acronyms, and technical terminology. Do not add punctuation.", }, // "provider": "custom.remote_whisper", "custom": { "remote_whisper": { "kind": "openai_audio_transcriptions", "label": "Remote whisper.cpp",

Extension points exported contracts — how you extend this code

InputEventSource (Interface)
(no doc) [2 implementers]
src/input/source.rs
ItnEngine (Interface)
(no doc) [2 implementers]
src/text/normalize.rs

Core symbols most depended-on inside this repo

is_empty
called by 103
src/audio/vad.rs
len
called by 84
src/audio/capture.rs
trim
called by 64
src/audio/vad.rs
elapsed
called by 22
tests/tip/resource_timeline.rs
get
called by 19
src/config.rs
as_str
called by 16
src/input/paste_route.rs
clear
called by 11
src/input/manager.rs
name
called by 11
src/text/normalize.rs

Shape

Function 300
Method 297
Class 117
Enum 31
Interface 2

Languages

Rust100%

Modules by API surface

src/config.rs95 symbols
src/audio/vad.rs37 symbols
src/input/manager.rs33 symbols
src/app.rs25 symbols
tests/tip/live_harness.rs23 symbols
tests/tip/diff_report.rs23 symbols
src/transcription/custom_openai.rs23 symbols
src/text/normalize.rs23 symbols
src/logging.rs23 symbols
src/input/shortcuts.rs23 symbols
src/benchmark.rs23 symbols
tests/tip/bench_report.rs22 symbols

For agents

$ claude mcp add hyprwhspr-rs \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page