Give throwing a recording away a key of its own

Stopping a recording is the step there is no taking back. It is what sends the
audio off, and a moment later the sentence you did not mean to dictate is in
the clipboard and pasted into whatever window you were typing in. The tray menu
was the only way out, and by the time it is open the recording has already
gone. Discarding needed to be as quick as starting, which means a key.

Ctrl+Alt+Space rather than Escape: the combination the recording started with,
one modifier along, so the two are one gesture with a modifier between them.
Escape is the obvious choice and the wrong one, because it belongs to whichever
window has focus, and while you are talking something else usually has it. It
works on a dictation and on a command for the agent alike, since the one you
want to take back is the one that is running.

That made four global shortcuts, and four is the number at which three copies
of the same forty lines stop being a coincidence. The command line already had
a table of them; hotkey.py now holds it, and the settings window reads it too,
so a shortcut is a row rather than a combination box, two buttons, a status
label and three methods written out again. The window no longer takes the
commands to run as arguments either, because ipc.command_for already knows them
from the verb. Adding the fourth key is what this buys: one line in the table
and one call per row.

Both places a key can live are told about it. The listener catching the press
and the KDE shortcut arriving behind it go through the same echo guard the
toggle already had, so the two are one discard rather than two, and the tray
calls the inner method as it already did for the toggle. Ctrl+Space and
Ctrl+Alt+Space land on the same evdev key code, so there is a test for the
modifier matching that keeps them apart.

install.sh takes the second key as a second argument and refuses to register
two of the same. It hands both to `dikte shortcut install` rather than writing
kglobalshortcutsrc itself, which is what makes it work on GNOME, and what
finally puts the chosen key in the settings as well: the built-in listener
reads it from there, so a key written to only one of the two places was a key
that half worked.

Left empty in the settings window the discard key stays empty, unlike the
dictation shortcut which falls back to Ctrl+Space: a recording can always be
thrown away from the tray menu, so there is nothing to guarantee here.
This commit is contained in:
yusufipk
2026-08-01 18:57:50 +03:00
parent 1e959178b4
commit 3486892ec6
12 changed files with 302 additions and 230 deletions
+39 -23
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Dikte installer: dependency check, launchers, KDE shortcut.
# Dikte installer: dependency check, launchers, global shortcuts.
set -euo pipefail
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -8,6 +8,9 @@ BIN_DIR="$HOME/.local/bin"
APP_DIR="$HOME/.local/share/applications"
AUTOSTART_DIR="$HOME/.config/autostart"
SHORTCUT="${1:-Ctrl+Space}"
# Without the colon, so that a second argument given as "" stays empty. That is
# how update.sh says "this one was turned off", as against not saying anything.
CANCEL_SHORTCUT="${2-Ctrl+Alt+Space}"
say() { printf ' %s\n' "$1"; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
@@ -88,30 +91,43 @@ StartupNotify=false
EOF
ok "Will start automatically on login"
# 4. KDE global shortcut ---------------------------------------------------
cat > "$APP_DIR/dikte-toggle.desktop" <<EOF
[Desktop Entry]
Exec=$PY $DIR/dikte.py toggle
Name=Dikte: start/stop recording
NoDisplay=true
StartupNotify=false
Type=Application
X-KDE-GlobalAccel-CommandShortcut=true
EOF
# 4. Global shortcuts ------------------------------------------------------
# Two of them: one to start and stop, one to throw the recording away. The
# second is worth a key of its own because stopping is the step there is no
# taking back, being what sends the audio off to be transcribed.
#
# Dikte registers them rather than this script writing the files itself: it
# knows which desktop it is on, and it stores the combination in the settings
# as well, which is where the built-in listener reads it from. A key written
# to only one of the two places is a key that half works.
if [[ "$SHORTCUT" == "$CANCEL_SHORTCUT" ]]; then
warn "Both arguments are $SHORTCUT, so the discard key was left out."
say "Pass two different combinations, or set it in Settings → Shortcuts."
CANCEL_SHORTCUT=""
fi
if [[ "${XDG_CURRENT_DESKTOP:-}" == *GNOME* || "${XDG_CURRENT_DESKTOP:-}" == *gnome* ]]; then
ok "GNOME detected"
say "Open Dikte Settings > Shortcut to install the global shortcut."
elif command -v kwriteconfig6 >/dev/null; then
kwriteconfig6 --notify --file kglobalshortcutsrc \
--group services --group dikte-toggle.desktop \
--key _launch "$SHORTCUT"
ok "KDE shortcut registered: $SHORTCUT"
warn "KWin only reads this at startup, so the shortcut goes live after your"
say "next login. Until then open Settings → Shortcut and turn on the"
say "built-in listener to use it right away."
register() { # which combination label
if out="$("$PY" "$DIR/dikte.py" shortcut install "$1" --combo "$2" 2>&1)"; then
ok "$3: $2"
else
# One line: the rest of what it has to say about KWin is printed below.
warn "${out%%$'\n'*}"
fi
}
if python3 -c 'import PyQt6.QtWidgets' 2>/dev/null; then
register toggle "$SHORTCUT" "Start and stop"
if [[ -n "$CANCEL_SHORTCUT" ]]; then
register cancel "$CANCEL_SHORTCUT" "Discard the recording"
fi
if [[ "${XDG_CURRENT_DESKTOP:-}" != *[Gg][Nn][Oo][Mm][Ee]* ]]; then
warn "KWin only reads these at startup, so they go live after your next"
say "login. Until then open Settings → Shortcuts and turn on the"
say "built-in listener to use them right away."
fi
else
warn "No supported shortcut manager found. Add the shortcut in desktop settings."
warn "PyQt6 is missing, so no shortcut was registered. Install it, then run:"
say "dikte shortcut install toggle --combo '$SHORTCUT'"
fi
echo