From 0d42f1bafe1196ea78a9a4fd5406c4250146750c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 6 Jan 2026 13:00:19 +0100 Subject: [PATCH] Add voice dictation with voxtype (#4088) * Try with voxtype * Update delay to prevent skipped characters * Add removal of voxtype * Use -bin package * Fix for yay * Nerdicons! * Use new, subtle nerdfont glyphs instead of standard icons * Do this in the waybar config instead * Make voxtype a permanent fixture so it is not lost on waybar resets * Record purpose * Add Dictation hotkeys * Tweak wording and point to config * Reuse the same help * Better communication * Anticipate moving the package to OPR * Clarify where the packages are coming from * input group not needed when using hyprland hotkey * Explain hotkey off * Allow for changing of the model on right click * Allow config on right click * Be more specific * Focus on config editing with waybar clicks --------- Co-authored-by: Ryan Hughes --- bin/omarchy-menu | 7 +- bin/omarchy-pkg-aur-add | 17 +++++ bin/omarchy-voxtype-config | 6 ++ bin/omarchy-voxtype-install | 19 ++++++ bin/omarchy-voxtype-model | 5 ++ bin/omarchy-voxtype-remove | 20 ++++++ bin/omarchy-voxtype-status | 9 +++ config/waybar/config.jsonc | 15 ++++- config/waybar/style.css | 8 ++- default/hypr/bindings/utilities.conf | 6 +- default/voxtype/config.toml | 97 ++++++++++++++++++++++++++++ migrations/1767685679.sh | 60 +++++++++++++++++ 12 files changed, 263 insertions(+), 6 deletions(-) create mode 100755 bin/omarchy-pkg-aur-add create mode 100755 bin/omarchy-voxtype-config create mode 100755 bin/omarchy-voxtype-install create mode 100755 bin/omarchy-voxtype-model create mode 100755 bin/omarchy-voxtype-remove create mode 100755 bin/omarchy-voxtype-status create mode 100644 default/voxtype/config.toml create mode 100644 migrations/1767685679.sh diff --git a/bin/omarchy-menu b/bin/omarchy-menu index e813d978..c17ddf11 100755 --- a/bin/omarchy-menu +++ b/bin/omarchy-menu @@ -278,8 +278,8 @@ show_install_ai_menu() { echo ollama ) - case $(menu "Install" " Dictation [AUR]\n󱚤 Claude Code\n󱚤 Copilot CLI [AUR]\n󱚤 Cursor CLI\n󱚤 Gemini\n󱚤 OpenAI Codex\n󱚤 LM Studio\n󱚤 Ollama\n󱚤 Crush") in - *Dictation*) present_terminal "echo 'Installing Hyprwhspr from AUR...'; yay -S --noconfirm hyprwhspr && hyprwhspr setup" ;; + case $(menu "Install" " Dictation\n󱚤 Claude Code\n󱚤 Copilot CLI [AUR]\n󱚤 Cursor CLI\n󱚤 Gemini\n󱚤 OpenAI Codex\n󱚤 LM Studio\n󱚤 Ollama\n󱚤 Crush") in + *Dictation*) present_terminal omarchy-voxtype-install ;; *Claude*) install "Claude Code" "claude-code" ;; *Copilot*) aur_install "Copilot CLI" "github-copilot-cli" ;; *Cursor*) install "Cursor CLI" "cursor-cli" ;; @@ -368,11 +368,12 @@ show_install_elixir_menu() { } show_remove_menu() { - case $(menu "Remove" "󰣇 Package\n Web App\n TUI\n󰵮 Development\n󰸌 Theme\n󰍲 Windows\n󰈷 Fingerprint\n Fido2") in + case $(menu "Remove" "󰣇 Package\n Web App\n TUI\n󰵮 Development\n Dictation\n󰸌 Theme\n󰍲 Windows\n󰈷 Fingerprint\n Fido2") in *Package*) terminal omarchy-pkg-remove ;; *Web*) present_terminal omarchy-webapp-remove ;; *TUI*) present_terminal omarchy-tui-remove ;; *Development*) show_remove_development_menu ;; + *Dictation*) present_terminal omarchy-voxtype-remove ;; *Theme*) present_terminal omarchy-theme-remove ;; *Windows*) present_terminal "omarchy-windows-vm remove" ;; *Fingerprint*) present_terminal "omarchy-setup-fingerprint --remove" ;; diff --git a/bin/omarchy-pkg-aur-add b/bin/omarchy-pkg-aur-add new file mode 100755 index 00000000..332a0f60 --- /dev/null +++ b/bin/omarchy-pkg-aur-add @@ -0,0 +1,17 @@ +#!/bin/bash + +# Add the named packages to the system from the AUR if they're missing. Returns false if it couldn't be done. + +if omarchy-pkg-missing "$@"; then + yay -S --noconfirm --needed "$@" || exit 1 +fi + +for pkg in "$@"; do + # Secondary check to handle states where pacman doesn't actually register an error + if ! pacman -Q "$pkg" &>/dev/null; then + echo -e "\033[31mError: Package '$pkg' did not install\033[0m" >&2 + exit 1 + fi +done + +exit 0 diff --git a/bin/omarchy-voxtype-config b/bin/omarchy-voxtype-config new file mode 100755 index 00000000..78a3254f --- /dev/null +++ b/bin/omarchy-voxtype-config @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +# Used by Voxtype waybar module to open config on right click + +exec omarchy-launch-editor ~/.config/voxtype/config.toml diff --git a/bin/omarchy-voxtype-install b/bin/omarchy-voxtype-install new file mode 100755 index 00000000..28e75a0b --- /dev/null +++ b/bin/omarchy-voxtype-install @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +# Install voxtype and configure it for use. + +if gum confirm "Install Voxtype + AI model (~400MB) to enable dictation?"; then + omarchy-pkg-add wtype + omarchy-pkg-aur-add voxtype-bin + + # Setup voxtype + mkdir -p ~/.config/voxtype + cp $OMARCHY_PATH/default/voxtype/config.toml ~/.config/voxtype/ + + voxtype setup --download + voxtype setup systemd + + omarchy-restart-waybar + notify-send " Voxtype Dictation Ready" "Hold Super + Ctrl + X to dictate.\nEdit ~/.config/voxtype/config.toml for options." -t 10000 +fi diff --git a/bin/omarchy-voxtype-model b/bin/omarchy-voxtype-model new file mode 100755 index 00000000..b58047d4 --- /dev/null +++ b/bin/omarchy-voxtype-model @@ -0,0 +1,5 @@ +#!/bin/bash +set -e + +omarchy-launch-floating-terminal-with-presentation "voxtype setup model" +omarchy-restart-waybar diff --git a/bin/omarchy-voxtype-remove b/bin/omarchy-voxtype-remove new file mode 100755 index 00000000..b2083d15 --- /dev/null +++ b/bin/omarchy-voxtype-remove @@ -0,0 +1,20 @@ +#!/bin/bash +set -e + +# Remove voxtype and its configurations. + +if omarchy-cmd-present voxtype; then + echo "Uninstall Voxtype to remove dictation." + + # Remove services + systemctl --user stop voxtype.service 2>/dev/null || true + rm -f ~/.config/systemd/user/voxtype* + systemctl --user daemon-reload + + # Remove packages and configs + omarchy-pkg-drop wtype voxtype-bin + rm -rf ~/.config/voxtype + rm -rf ~/.local/share/voxtype +else + echo "Voxtype was not installed." +fi diff --git a/bin/omarchy-voxtype-status b/bin/omarchy-voxtype-status new file mode 100755 index 00000000..039ebca7 --- /dev/null +++ b/bin/omarchy-voxtype-status @@ -0,0 +1,9 @@ +#!/bin/bash + +if omarchy-cmd-present voxtype; then + voxtype status --follow --extended --format json | while read -r line; do + echo "$line" | jq -c '. + {alt: .class}' + done +else + echo '{"alt": "", "tooltip": ""}' +fi diff --git a/config/waybar/config.jsonc b/config/waybar/config.jsonc index 20cad4c0..1b9f5ba2 100644 --- a/config/waybar/config.jsonc +++ b/config/waybar/config.jsonc @@ -5,7 +5,7 @@ "spacing": 0, "height": 26, "modules-left": ["custom/omarchy", "hyprland/workspaces"], - "modules-center": ["clock", "custom/update", "custom/screenrecording-indicator"], + "modules-center": ["clock", "custom/update", "custom/voxtype", "custom/screenrecording-indicator"], "modules-right": [ "group/tray-expander", "bluetooth", @@ -140,6 +140,19 @@ "signal": 8, "return-type": "json" }, + "custom/voxtype": { + "exec": "omarchy-voxtype-status", + "return-type": "json", + "format": "{icon}", + "format-icons": { + "idle": "", + "recording": "󰍬", + "transcribing": "󰔟" + }, + "tooltip": true, + "on-click-right": "omarchy-voxtype-config", + "on-click": "omarchy-voxtype-model" + }, "tray": { "icon-size": 12, "spacing": 17 diff --git a/config/waybar/style.css b/config/waybar/style.css index 7b7a980e..c8315f66 100644 --- a/config/waybar/style.css +++ b/config/waybar/style.css @@ -74,10 +74,16 @@ tooltip { #custom-screenrecording-indicator { min-width: 12px; - margin-left: 8.75px; + margin-left: 5px; font-size: 10px; + padding-bottom: 1px; } #custom-screenrecording-indicator.active { color: #a55555; } + +#custom-voxtype { + min-width: 12px; + margin: 0 0 0 7.5px; +} diff --git a/default/hypr/bindings/utilities.conf b/default/hypr/bindings/utilities.conf index bdd1a6ba..59753f0b 100644 --- a/default/hypr/bindings/utilities.conf +++ b/default/hypr/bindings/utilities.conf @@ -49,4 +49,8 @@ bindd = SUPER CTRL ALT, B, Show battery remaining, exec, notify-send "󰁹 Ba bindd = SUPER CTRL, A, Audio controls, exec, omarchy-launch-audio bindd = SUPER CTRL, B, Bluetooth controls, exec, omarchy-launch-bluetooth bindd = SUPER CTRL, W, Wifi controls, exec, omarchy-launch-wifi -bindd = SUPER CTRL, T, Activity, exec, omarchy-launch-tui btop \ No newline at end of file +bindd = SUPER CTRL, T, Activity, exec, omarchy-launch-tui btop + +# Dictation +bindd = SUPER CTRL, X, Start dictation, exec, voxtype record start +binddr = SUPER CTRL, X, Stop dictation, exec, voxtype record stop diff --git a/default/voxtype/config.toml b/default/voxtype/config.toml new file mode 100644 index 00000000..59d1f943 --- /dev/null +++ b/default/voxtype/config.toml @@ -0,0 +1,97 @@ +# Voxtype Configuration +# +# Location: ~/.config/voxtype/config.toml +# All settings can be overridden via CLI flags +# +# State file for external integrations (Waybar, polybar, etc.) +# Use "auto" for default location ($XDG_RUNTIME_DIR/voxtype/state), +# a custom path, or "disabled" to turn off. The daemon writes state +# ("idle", "recording", "transcribing") to this file whenever it changes. +# Required for `voxtype record toggle` and `voxtype status` commands. +state_file = "auto" + +[hotkey] +# Hotkey is configured in Hyprland. Default is Super + Ctrl + X +enabled = false + +[audio] +# Audio input device ("default" uses system default) +# List devices with: pactl list sources short +device = "default" + +# Sample rate in Hz (whisper expects 16000) +sample_rate = 16000 + +# Maximum recording duration in seconds (safety limit) +max_duration_secs = 60 + +# [audio.feedback] +# Enable audio feedback sounds (beeps when recording starts/stops) +# enabled = true +# +# Sound theme: "default", "subtle", "mechanical", or path to custom theme directory +# theme = "default" +# +# Volume level (0.0 to 1.0) +# volume = 0.7 + +[whisper] +# Model to use for transcription +# Options: tiny, tiny.en, base, base.en, small, small.en, medium, medium.en, large-v3, large-v3-turbo +# .en models are English-only but faster and more accurate for English +# large-v3-turbo is faster than large-v3 with minimal accuracy loss (recommended for GPU) +# Or provide absolute path to a custom .bin model file +model = "base.en" + +# Language for transcription +# Use "en" for English, "auto" for auto-detection +# See: https://github.com/openai/whisper#available-models-and-languages +language = "en" + +# Translate non-English speech to English +translate = false + +# Number of CPU threads for inference (omit for auto-detection) +# threads = 4 + +[output] +# Primary output mode: "type" or "clipboard" +# - type: Simulates keyboard input at cursor position (requires ydotool) +# - clipboard: Copies text to clipboard (requires wl-copy) +mode = "type" + +# Fall back to clipboard if typing fails +fallback_to_clipboard = true + +# Delay between typed characters in milliseconds +# 0 = fastest possible, increase if characters are dropped +type_delay_ms = 1 + +# Post-processing command (optional) +# Pipe transcribed text through an external command for cleanup before output. +# The command receives text on stdin and outputs processed text on stdout. +# Useful for LLM-based text cleanup, grammar correction, filler word removal. +# On any failure (timeout, error), falls back to original transcription. +# +# [output.post_process] +# command = "ollama run llama3.2:1b 'Clean up this dictation. Fix grammar, remove filler words. Output only the cleaned text:'" +# timeout_ms = 30000 # 30 second timeout (generous for LLM) + +[output.notification] +# Show notification when recording starts (hotkey pressed) +on_recording_start = false + +# Show notification when recording stops (transcription beginning) +on_recording_stop = false + +# Show notification with transcribed text after transcription completes +on_transcription = false + +# [text] +# Text processing options (word replacements, spoken punctuation) +# +# Enable spoken punctuation conversion (e.g., say "period" to get ".") +# spoken_punctuation = false +# +# Custom word replacements (case-insensitive) +# replacements = { "hyperwhisper" = "hyprwhspr" } diff --git a/migrations/1767685679.sh b/migrations/1767685679.sh new file mode 100644 index 00000000..36f5b6ce --- /dev/null +++ b/migrations/1767685679.sh @@ -0,0 +1,60 @@ +echo "Add voxtype to waybar" + +patch -N ~/.config/waybar/style.css << 'EOF' +--- a/waybar/style.css ++++ b/waybar/style.css +@@ -74,10 +74,16 @@ tooltip { + + #custom-screenrecording-indicator { + min-width: 12px; +- margin-left: 8.75px; ++ margin-left: 5px; + font-size: 10px; ++ padding-bottom: 1px; + } + + #custom-screenrecording-indicator.active { + color: #a55555; + } ++ ++#custom-voxtype { ++ min-width: 12px; ++ margin: 0 0 0 7.5px; ++} +EOF + +patch -N ~/.config/waybar/config.jsonc << 'EOF' +--- a/waybar/config.jsonc ++++ b/waybar/config.jsonc +@@ -5,7 +5,7 @@ + "spacing": 0, + "height": 26, + "modules-left": ["custom/omarchy", "hyprland/workspaces"], +- "modules-center": ["clock", "custom/update", "custom/screenrecording-indicator"], ++ "modules-center": ["clock", "custom/update", "custom/voxtype", "custom/screenrecording-indicator"], + "modules-right": [ + "group/tray-expander", + "bluetooth", +@@ -140,6 +140,19 @@ + "signal": 8, + "return-type": "json" + }, ++ "custom/voxtype": { ++ "exec": "omarchy-voxtype-status", ++ "return-type": "json", ++ "format": "{icon}", ++ "format-icons": { ++ "idle": "", ++ "recording": "󰍬", ++ "transcribing": "󰔟" ++ }, ++ "tooltip": true, ++ "on-click-right": "omarchy-voxtype-config", ++ "on-click": "omarchy-voxtype-model" ++ }, + "tray": { + "icon-size": 12, + "spacing": 17 +EOF + +omarchy-restart-waybar