Files
command-center/omp-docs/tools/handy.md
Frank b441cfda56 docs(DE): full redesign sweep — desk profiles, keybinds, noctalia v5, handy fleet wiring, incident log
- niri: index/config-tree rework, displays.md desk-profile rewrite,
  tips/action-reference/default-bindings refresh (26.04),
  generated keybindings.md (119 bindings)
- noctalia-shell.md: settings.toml lives in ~/.local/state, taskbar
  widget, v5 keybind table (Mod+D/Mod+V/Alt+Tab)
- handy.md: NEW; live nested store shape, fleet endpoint, known
  thinking-model empty-content breaker + verified cure
- log.md: DE redesign entry + pam_faillock sudo-lockout incident
2026-10-09 14:42:08 -07:00

6.9 KiB

type, title, description, resource, tags, timestamp, verified
type title description resource tags timestamp verified
reference Handy Offline speech-to-text transcription with WebKitGTK backend and Tauri v2 UI. https://github.com/cjpais/Handy
handy
stt
speech-to-text
transcription
wayland
tauri
2026-10-09 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:

{ "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

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:

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

// 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:

export WEBKIT_DISABLE_DMABUF_RENDERER=1
handy

SIGUSR1 Crash

Trap: The upstream SIGUSR1 handler was REMOVED (WebKitGTK now owns it).

# WRONG (may crash the app):
pkill -USR1 handy

# CORRECT (toggle transcription):
pkill -USR2 -n handy
# or use CLI:
handy --toggle-transcription

Debug Mode

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.

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