--- type: reference title: Handy description: Offline speech-to-text transcription with WebKitGTK backend and Tauri v2 UI. resource: https://github.com/cjpais/Handy tags: - handy - stt - speech-to-text - transcription - wayland - tauri timestamp: 2026-10-09 verified: 2026-10-09 --- # Handy Handy is an offline speech-to-text (STT) transcription tool built with Tauri v2 and WebKitGTK. It runs entirely offline, supports multiple GPU backends, and provides real-time transcription with optional post-processing. **Installed version:** 0.9.6-1 (`local/handy-bin`, `/usr/bin/handy`) — **0.9.8 upgrade pending**: `yay -Syu handy-bin` (root work is operator-owned; agents never sudo). **Upstream latest:** 0.9.8 (as of 2026-10-03) **Binary:** `/usr/bin/handy` **Config:** `~/.config/com.pais.handy/` (upstream docs) / `~/.local/share/com.pais.handy/settings_store.json` (live observed) --- ## Architecture Handy uses Tauri v2 for its UI (WebKitGTK for rendering) and a native Rust backend for STT processing. On Wayland, it requires compositor-level global shortcuts. **Live settings snapshot (blankpad):** | Setting | Value | |---------|-------| | `paste_method` | `none` | | `copy_to_clipboard` | `true` | | `push_to_talk` | `true` | | `bindings` | transcribe=`ctrl+space`, transcribe_with_post_process=`ctrl+shift+space`, cancel=`escape` | | `keyboard_implementation` | `handy_keys` | | `overlay` | `bottom` (live), `true` (config) | | `model` | `handy-computer/canary-180m-flash-gguf` (Q8_0) | | `transcribe_gpu` | `vulkan` (PCI 07:00.0) | | `autostart_enabled` | `false` (niri-managed) | | `experimental` | `true` | | `filler_word_removal` | `true` | | `post_process_enabled` | `true` (fleet-wired) | | `clipboard_handling` | `copy_to_clipboard` | | `provider` | `custom` (base_url=`http://192.168.0.143:7771/v1`, model=`frank-secondary`) | | `prompt` | `default_improve_transcriptions` | --- ## Configuration ### Config Location **Upstream docs:** `~/.config/com.pais.handy/` **Live observed:** `~/.local/share/com.pais.handy/settings_store.json` Both locations may contain settings; the live data directory is the authoritative runtime store. ### Live Settings (blankpad) The store is nested: everything lives under a top-level `settings` object. Post-processing wiring (applied 2026-10-09) touches five keys plus the `custom` entry inside `post_process_providers`: ```json { "settings": { "post_process_enabled": true, "post_process_provider_id": "custom", "post_process_models": { "custom": "frank-secondary", "openai": "", "...": "" }, "post_process_selected_prompt_id": "default_improve_transcriptions", "post_process_providers": [ { "id": "custom", "base_url": "http://192.168.0.143:7771/v1", "allow_base_url_edit": true, "models_endpoint": "/models", "...": "" } ], "post_process_api_keys": { "custom": "", "...": "" }, "autostart_enabled": false, "push_to_talk": true, "paste_method": "none", "clipboard_handling": "copy_to_clipboard", "keyboard_implementation": "handy_keys", "overlay_position": "bottom", "overlay_style": "live" } } ``` Repo-side merge patch + apply procedure: `dotfiles/zz-blankpad/.local/share/com.pais.handy/` (README.md). --- ## Command-Line Interface ### Startup Flags ```bash handy --start-hidden # Start without showing the UI handy --no-tray # Hide the system tray icon handy --debug # Enable debug mode (Ctrl+Shift+D) ``` ### Remote Control Send commands to a running instance: ```bash handy --toggle-transcription # Toggle transcription on/off handy --toggle-post-process # Toggle post-processing on/off handy --cancel # Cancel the current transcription ``` --- ## Wayland Integration ### Global Shortcuts On Wayland, global shortcuts **must** be defined in the compositor (niri), not in Handy itself. **File:** `~/.config/niri/cfg/keybinds.kdl` ```kdl // Live (cfg/keybinds.kdl): Mod+Shift+Space hotkey-overlay-title="Handy: toggle post-process recording" allow-when-locked=true { spawn-sh "handy --toggle-post-process"; } // Transcription itself stays on Handy's own ctrl+space (keyboard_implementation="handy_keys"). ``` ### Text Input **Requirement:** `wtype` or `dotool` for text input (NOT installed on blankpad). **Current config:** `paste_method=none` + manual `Ctrl+V` paste. ### Overlay **Default:** Disabled on Linux (can steal focus and break paste). **Live config:** `mode=bottom`, `live=true`. --- ## Troubleshooting ### WebKit Renderer Issue **Symptom:** Blank or corrupted rendering. **Fix:** ```bash export WEBKIT_DISABLE_DMABUF_RENDERER=1 handy ``` ### SIGUSR1 Crash **Trap:** The upstream SIGUSR1 handler was **REMOVED** (WebKitGTK now owns it). ```bash # WRONG (may crash the app): pkill -USR1 handy # CORRECT (toggle transcription): pkill -USR2 -n handy # or use CLI: handy --toggle-transcription ``` ### Debug Mode ```bash handy --debug # or press Ctrl+Shift+D in a running instance ``` --- ## Dependencies | Package | Purpose | |---------|---------| | `handy` | Main application | | `tauri-apps` | Tauri v2 runtime | | `webkit2gtk` | WebKitGTK rendering | | `gtk-layer-shell` | Wayland layer-shell support | | `vulkan` | GPU-accelerated STT | | `ffmpeg` | Audio processing | --- ## Fleet LLM Endpoint **Post-processing pipeline:** `handy` → `http://192.168.0.143:7771/v1`, model id `frank-secondary` (the served id; harness callsign for the same unit is `frank-zealot` — same llama.cpp server, also the fleet compaction endpoint). **Endpoint configuration:** - **Provider:** `custom` (the one preset with `allow_base_url_edit: true`) - **Base URL:** `http://192.168.0.143:7771/v1` - **Model:** `frank-secondary` - **Prompt:** `default_improve_transcriptions` ### Known issues **Thinking-model empty content (2026-10-09, open).** `frank-secondary` is a thinking model. Default chat completions return `content: ""` with the answer trapped in `reasoning_content` — Handy reads `content`, so post-processed transcripts arrive empty. Verified per-request cure: `"chat_template_kwargs": {"enable_thinking": false}` (probe below returns cleaned text in `content`). Handy has no field to send extra body keys, and the cure CANNOT be set server-global on :7771 without degrading fleet compaction (same server). Proposed fix (not yet implemented): a dedicated no-think unit/port for Handy traffic, e.g. llama-server on `:7772` with thinking disabled at the template level, then repoint `custom.base_url`. ```bash curl -s http://192.168.0.143:7771/v1/chat/completions -d '{ "model": "frank-secondary", "messages": [{"role":"user","content":"Clean this transcript: twenty five items cost ten dollars um ok."}], "max_tokens": 200, "chat_template_kwargs": {"enable_thinking": false}}' ``` See [[noctalia-shell]] for IPC integration patterns. --- ## See Also - [[noctalia-shell|noctalia-shell.md]] — Desktop environment integration - [[niri|niri/index.md]] — Compositor configuration - [`handy --help`](https://github.com/cjpais/Handy) — CLI reference