diff options
| author | 2026-05-13 13:43:23 +0100 | |
|---|---|---|
| committer | 2026-05-13 13:43:23 +0100 | |
| commit | 5c241d65ed4a6ec2bc3e5d75d6858ed6722f1b17 (patch) | |
| tree | 9885503fbde0324c04e51c6cd70bce143c4a8355 | |
| parent | d2da7ff900f360c0eec1e17f62df1358651ed77c (diff) | |
| download | dotfiles-5c241d65ed4a6ec2bc3e5d75d6858ed6722f1b17.tar.gz dotfiles-5c241d65ed4a6ec2bc3e5d75d6858ed6722f1b17.tar.bz2 dotfiles-5c241d65ed4a6ec2bc3e5d75d6858ed6722f1b17.zip | |
feat(sway): add dictate (whisper.cpp) and ocr (tesseract) keybinds
Push-to-talk dictation toggle on Super+i: parecord captures 16 kHz mono
WAV, whisper-cli transcribes (auto language), output is typed via wtype
and copied to the clipboard.
Region OCR on Super+Shift+o: slurp + grim feed tesseract (eng+por),
result lands in the clipboard with a notification preview.
Adds wtype to wayland.txt; tesseract (+eng/por data) and whisper.cpp +
the large-v3-turbo-q5_0 model package to extra.txt.
| -rw-r--r-- | KEYBINDS.md | 2 | ||||
| -rw-r--r-- | dot_config/sway/config | 6 | ||||
| -rw-r--r-- | dot_local/bin/executable_dictate | 87 | ||||
| -rw-r--r-- | dot_local/bin/executable_ocr | 39 | ||||
| -rw-r--r-- | meta/extra.txt | 9 | ||||
| -rw-r--r-- | meta/wayland.txt | 3 |
6 files changed, 146 insertions, 0 deletions
diff --git a/KEYBINDS.md b/KEYBINDS.md index 1988f6e..3f203c1 100644 --- a/KEYBINDS.md +++ b/KEYBINDS.md @@ -324,6 +324,8 @@ Mod key: `Super` (Mod4). Only personal additions beyond sway defaults listed. | `XF86RFKill` | Toggle all radios (rfkill) | | `Print` | Region screenshot (grim+slurp) | | `Shift+Print` | Full screenshot (grim) | +| `Super+i` | Dictate toggle (whisper.cpp โ wtype + clipboard) | +| `Super+Shift+o` | OCR region (tesseract โ clipboard) | | `Super+Shift+s` | Lock screen + pause media | | `Super+n` | Dismiss notification | | `Super+Shift+n` | Dismiss all notifications | diff --git a/dot_config/sway/config b/dot_config/sway/config index edf1e32..c1d897f 100644 --- a/dot_config/sway/config +++ b/dot_config/sway/config @@ -157,6 +157,12 @@ bindsym XF86RFKill exec rfkill toggle all bindsym Print exec sh -c 'grim -g "$(slurp)" - | tee ~/pics/screenshots/$(date +%Y-%m-%d-%H-%M-%S).png | wl-copy' bindsym Shift+Print exec sh -c 'grim - | tee ~/pics/screenshots/$(date +%Y-%m-%d-%H-%M-%S).png | wl-copy' +# Dictation (push-to-talk toggle, whisper.cpp) +bindsym $mod+i exec ~/.local/bin/dictate + +# OCR โ crop a region, copy text to clipboard (tesseract) +bindsym $mod+Shift+o exec ~/.local/bin/ocr + # Lock & pause bindsym $mod+Shift+s exec "playerctl -a pause; swaylock -f -e -c 282828" diff --git a/dot_local/bin/executable_dictate b/dot_local/bin/executable_dictate new file mode 100644 index 0000000..2e8a361 --- /dev/null +++ b/dot_local/bin/executable_dictate @@ -0,0 +1,87 @@ +#!/usr/bin/env sh +# Push-to-talk dictation. Toggle: 1st invocation starts recording; +# 2nd stops, transcribes via whisper.cpp, types the result into the focused +# window with wtype, and copies it to the clipboard. +# +# Pacman: pipewire-pulse (parecord), wtype, wl-clipboard, libnotify +# AUR: whisper.cpp, whisper.cpp-model-large-v3-turbo-q5_0 +# +# Override via env: +# WHISPER_MODEL path to a ggml-*.bin model +# WHISPER_LANG language code or 'auto' (default: auto) + +set -eu + +state_dir="${XDG_RUNTIME_DIR:-/tmp}/dictate" +pid_file="$state_dir/pid" +wav_file="$state_dir/audio.wav" +log_file="$state_dir/whisper.log" +default_model='/usr/share/whisper.cpp-model-large-v3-turbo-q5_0/ggml-large-v3-turbo-q5_0.bin' +model="${WHISPER_MODEL:-$default_model}" +lang="${WHISPER_LANG:-auto}" + +mkdir -p "$state_dir" + +is_recording() { + [ -r "$pid_file" ] && kill -0 "$(cat "$pid_file")" 2>/dev/null +} + +start_recording() { + if ! command -v whisper-cli >/dev/null; then + notify-send -u critical "๐๏ธ dictate" \ + "whisper-cli not found. Install whisper.cpp (AUR)." + exit 1 + fi + if [ ! -r "$model" ]; then + notify-send -u critical "๐๏ธ dictate" \ + "Model missing: $model. Install whisper.cpp-model-large-v3-turbo-q5_0 (AUR)." + exit 1 + fi + rm -f "$wav_file" + parecord --format=s16le --rate=16000 --channels=1 "$wav_file" \ + >"$log_file" 2>&1 & + echo "$!" >"$pid_file" + notify-send -t 1500 "๐๏ธ Recordingโฆ" "Press the bind again to stop." +} + +stop_and_transcribe() { + pid="$(cat "$pid_file")" + rm -f "$pid_file" + kill -TERM "$pid" 2>/dev/null || true + # Give parecord up to 2s to flush the WAV header. + i=0 + while kill -0 "$pid" 2>/dev/null && [ "$i" -lt 20 ]; do + sleep 0.1 + i=$((i + 1)) + done + kill -KILL "$pid" 2>/dev/null || true + + if [ ! -s "$wav_file" ]; then + notify-send -u low "๐๏ธ dictate" "No audio captured." + exit 1 + fi + + notify-send -t 1500 "๐๏ธ Transcribingโฆ" + + text="$( + whisper-cli -m "$model" -f "$wav_file" \ + -l "$lang" -nt -np -t "$(nproc)" 2>"$log_file" \ + | tr -s '[:space:]' ' ' \ + | sed -e 's/^ //; s/ $//' + )" + + if [ -z "$text" ]; then + notify-send -u low "๐๏ธ dictate" "Empty transcription. See $log_file." + exit 1 + fi + + printf '%s' "$text" | wl-copy + wtype -- "$text" + notify-send -t 2500 "๐๏ธ Dictated" "$text" +} + +if is_recording; then + stop_and_transcribe +else + start_recording +fi diff --git a/dot_local/bin/executable_ocr b/dot_local/bin/executable_ocr new file mode 100644 index 0000000..6f6191f --- /dev/null +++ b/dot_local/bin/executable_ocr @@ -0,0 +1,39 @@ +#!/usr/bin/env sh +# OCR a screen region (default) or an image file โ clipboard. +# +# Usage: +# ocr # interactive: select a region with slurp, OCR it +# ocr <file> # OCR an image file +# +# Requires: tesseract (+tesseract-data-eng, tesseract-data-por), +# grim, slurp, wl-clipboard, libnotify. +# +# Override languages via TESSERACT_LANG (e.g. TESSERACT_LANG=eng). + +set -eu + +lang="${TESSERACT_LANG:-eng+por}" + +if [ "${1:-}" ]; then + [ -r "$1" ] || { notify-send -u critical "๐ OCR" "Cannot read: $1"; exit 1; } + text="$(tesseract "$1" - -l "$lang" 2>/dev/null || true)" +else + region="$(slurp 2>/dev/null)" || exit 0 + text="$(grim -g "$region" - | tesseract - - -l "$lang" 2>/dev/null || true)" +fi + +# Trim trailing whitespace per line; collapse runs of blank lines; drop +# leading blanks. +text="$(printf '%s\n' "$text" | awk ' + { sub(/[[:space:]]+$/, "") } + NF { print; blank = 0; next } + !blank { print; blank = 1 } +' | sed -e '/./,$!d')" + +if [ -z "$text" ]; then + notify-send -u low "๐ OCR" "No text detected." + exit 1 +fi + +printf '%s' "$text" | wl-copy +notify-send -t 3000 "๐ OCR copied" "$(printf '%s' "$text" | head -c 200)" diff --git a/meta/extra.txt b/meta/extra.txt index f6082d9..fb87c3d 100644 --- a/meta/extra.txt +++ b/meta/extra.txt @@ -3,3 +3,12 @@ pandoc-bin syncthing udisks2 autenticacao-gov-pt-bin + +# OCR (used by ~/.local/bin/ocr) +tesseract +tesseract-data-eng +tesseract-data-por + +# Speech-to-text (used by ~/.local/bin/dictate) +whisper.cpp +whisper.cpp-model-large-v3-turbo-q5_0 diff --git a/meta/wayland.txt b/meta/wayland.txt index fa0f26f..91d68b4 100644 --- a/meta/wayland.txt +++ b/meta/wayland.txt @@ -30,6 +30,9 @@ grim slurp wf-recorder +# Wayland typing (used by dictate, etc) +wtype + # Image viewer imv |
