Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ bindsym $mod+d exec talktype
2. Speak
3. Press the shortcut again → transcribes and types the text at your cursor

**Cancel:** Double-press the shortcut (press twice within ~1.5 seconds) to cancel
without transcribing. You can also press the shortcut during transcription to cancel.

## Backends

Server backends auto-start on first use — the model loads once and stays in
Expand Down
56 changes: 48 additions & 8 deletions talktype
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ TALKTYPE_DIR="${TALKTYPE_DIR:-${XDG_RUNTIME_DIR:-/tmp}/talktype}"
PIDFILE="$TALKTYPE_DIR/rec.pid"
AUDIOFILE="$TALKTYPE_DIR/rec.wav"
NOTIFYFILE="$TALKTYPE_DIR/notify.id"
TRANSFILE="$TALKTYPE_DIR/trans.pid"
STARTFILE="$TALKTYPE_DIR/rec.start"

mkdir -p "$TALKTYPE_DIR"

Expand Down Expand Up @@ -53,7 +55,11 @@ notify() {

notify_close() {
if [ -f "$NOTIFYFILE" ]; then
notify-send -a TalkType -r "$(cat "$NOTIFYFILE")" -e "TalkType" "" 2>/dev/null || true
gdbus call --session \
--dest org.freedesktop.Notifications \
--object-path /org/freedesktop/Notifications \
--method org.freedesktop.Notifications.CloseNotification \
"$(cat "$NOTIFYFILE")" &>/dev/null || true
rm -f "$NOTIFYFILE"
fi
}
Expand Down Expand Up @@ -125,6 +131,17 @@ check_deps() {

check_deps

# ── If transcription in progress → cancel it ──
if [ -f "$TRANSFILE" ]; then
PID=$(cat "$TRANSFILE")
rm -f "$TRANSFILE"
if kill -0 "$PID" 2>/dev/null; then
kill "$PID" 2>/dev/null || true
notify_close
exit 0
fi
fi

# ── If currently recording → stop, transcribe, type ──
if [ -f "$PIDFILE" ]; then
PID=$(cat "$PIDFILE")
Expand All @@ -133,19 +150,41 @@ if [ -f "$PIDFILE" ]; then
while kill -0 "$PID" 2>/dev/null; do sleep 0.05; done
rm -f "$PIDFILE"

notify process-working "Transcribing..."
# Double-press cancel: if recording was under 1.5 seconds, discard
if [ -f "$STARTFILE" ]; then
START=$(cat "$STARTFILE")
rm -f "$STARTFILE"
if NOW=$(date +%s%N 2>/dev/null) && [ $(( (NOW - START) / 1000000 )) -lt 1500 ]; then
rm -f "$AUDIOFILE"
notify_close
exit 0
fi
fi

# Run the transcription command with the audio file as last arg
TEXT=$($TALKTYPE_CMD "$AUDIOFILE")
notify_close

rm -f "$AUDIOFILE"
# Run transcription in background so it can be cancelled
TRANSOUT="$TALKTYPE_DIR/trans.out"
$TALKTYPE_CMD "$AUDIOFILE" > "$TRANSOUT" &
TRANS_PID=$!
echo "$TRANS_PID" > "$TRANSFILE"

if [ -z "$TEXT" ]; then
notify dialog-warning "No speech detected"
WAIT_STATUS=0
wait "$TRANS_PID" || WAIT_STATUS=$?

rm -f "$TRANSFILE" "$AUDIOFILE"

if [ "$WAIT_STATUS" -ne 0 ]; then
rm -f "$TRANSOUT"
exit 0
fi

notify_close
TEXT=$(cat "$TRANSOUT")
rm -f "$TRANSOUT"

if [ -z "$TEXT" ]; then
exit 0
fi

# Type text at cursor
type_text "$TEXT"
Expand All @@ -163,5 +202,6 @@ else
PID=$!
disown "$PID"
echo "$PID" > "$PIDFILE"
date +%s%N > "$STARTFILE" 2>/dev/null || true
notify audio-input-microphone "Listening..."
fi
7 changes: 6 additions & 1 deletion test/mocks/notify-send
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#!/usr/bin/env bash
# Mock notify-send: no-op
# Mock notify-send

# Support -p: output a fake notification ID
for arg in "$@"; do
[[ "$arg" == "-p" ]] && echo "42"
done
exit 0
60 changes: 54 additions & 6 deletions test/talktype.bats
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ setup() {
}

teardown() {
# Kill any leftover recording processes
if [ -f "$TALKTYPE_DIR/rec.pid" ]; then
kill "$(cat "$TALKTYPE_DIR/rec.pid")" 2>/dev/null || true
fi
# Kill any leftover processes
for pidfile in rec.pid trans.pid; do
if [ -f "$TALKTYPE_DIR/$pidfile" ]; then
kill "$(cat "$TALKTYPE_DIR/$pidfile")" 2>/dev/null || true
fi
done
rm -rf "$TALKTYPE_DIR"
}

Expand All @@ -32,6 +34,8 @@ start_fake_recording() {
sleep 300 &
echo $! > "$TALKTYPE_DIR/rec.pid"
echo "audio data" > "$TALKTYPE_DIR/rec.wav"
# Write a timestamp 10 seconds in the past (well above cancel threshold)
echo $(( $(date +%s%N) - 10000000000 )) > "$TALKTYPE_DIR/rec.start"
}

# ── Recording lifecycle ──
Expand Down Expand Up @@ -122,8 +126,8 @@ start_fake_recording() {

run "$TALKTYPE"

# Script should fail (set -e catches the non-zero exit)
[ "$status" -ne 0 ]
# Script exits cleanly — failure is caught by the wait pattern
[ "$status" -eq 0 ]

# No typing tool should have been called
[ ! -f "$TALKTYPE_DIR/ydotool.log" ]
Expand Down Expand Up @@ -238,6 +242,50 @@ start_fake_recording() {

# ── Dependency checking ──

# ── Cancel mechanisms ──

@test "double-press cancels recording without transcribing" {
start_fake_recording
# Overwrite with a fresh timestamp (just now — under 1 second)
date +%s%N > "$TALKTYPE_DIR/rec.start"

run "$TALKTYPE"
[ "$status" -eq 0 ]

# Should not have transcribed or typed
[ ! -f "$TALKTYPE_DIR/wtype.log" ]
[ ! -f "$TALKTYPE_DIR/ydotool.log" ]
# Audio file should be cleaned up
[ ! -f "$TALKTYPE_DIR/rec.wav" ]
}

@test "hotkey during transcription cancels it" {
# Create a fake transcription process
sleep 300 &
local trans_pid=$!
echo "$trans_pid" > "$TALKTYPE_DIR/trans.pid"

run "$TALKTYPE"
[ "$status" -eq 0 ]

# Transcription process should be killed
! kill -0 "$trans_pid" 2>/dev/null
[ ! -f "$TALKTYPE_DIR/trans.pid" ]
}

@test "stale trans.pid falls through to start recording" {
echo "99999" > "$TALKTYPE_DIR/trans.pid"

run "$TALKTYPE"
[ "$status" -eq 0 ]

# Should have started recording
[ -f "$TALKTYPE_DIR/rec.pid" ]
[ ! -f "$TALKTYPE_DIR/trans.pid" ]
}

# ── Dependency checking ──

@test "fails when no typing tool is available" {
# Create a minimal PATH with only recorder + notify (no typing tools)
local sparse="$BATS_TEST_TMPDIR/sparse_path"
Expand Down
Loading