Skip to content

feat(voice): global speak-selection hotkey + Settings row#127

Merged
StuBehan merged 3 commits into
mainfrom
feat/speak-selection-hotkey
Jul 13, 2026
Merged

feat(voice): global speak-selection hotkey + Settings row#127
StuBehan merged 3 commits into
mainfrom
feat/speak-selection-hotkey

Conversation

@StuBehan

Copy link
Copy Markdown
Collaborator

Summary

A global "speak selection" hotkey that reads the current selection aloud via stackvox — in any app. This is the "read anything, anywhere" surface alongside speaklast (reads Claude in the terminal) and the existing voice cues, all sharing the one local Kokoro voice.

Changes

  • SpeakSelection (panel/SpeakSelection.swift): captures the selection from the focused element's kAXSelectedTextAttribute, falling back to synthesizing ⌘C + reading the pasteboard (and restoring it) for apps that don't expose AX. Speaks it through Speaker with --normalize; re-pressing (or pressing with nothing selected) cancels the current read.
  • Configurable hotkeySTACKNUDGE_SPEAK_HOTKEY (default cmd+opt+s) + a "Read aloud shortcut" recorder row in Settings › Voice, mirroring the panel-hotkey recorder (record / persist / re-register live).
  • Hotkey now supports a second global hotkey: parameterized id, and each handler gates on its own id — returning eventNotHandledErr on a non-match so the two don't swallow each other's events (the panel hotkey keeps working).
  • Speaker: a --normalize option and cancel() (sends the daemon cancel command over the socket).

Testing

  • swiftc -typecheck panel/*.swift shared/*.swift — clean (0 errors).
  • macOS-only behaviour can't run in CI — manual steps to verify: Settings › Voice shows the "Read aloud shortcut" row at cmd+opt+s; select text in an app and press it → it reads; record a new combo → persists + works; and ⌘⌥N (panel) + Settings ↑/↓/Esc still behave while recording.

Related issues

The stop/interrupt (re-press) uses stackvox's cancel command, which shipped in stackvox 0.10.0 — so once the bundled daemon is on 0.10.0 (see the floor-bump PR), interrupting a read works; on older daemons it degrades to a no-op. Speaking works regardless.

StuBehan and others added 3 commits July 13, 2026 18:48
A configurable hotkey (STACKNUDGE_SPEAK_HOTKEY, default cmd+opt+s) reads the
current selection aloud via stackvox — in any app, not just the browser.

- SpeakSelection: captures the focused element's AX selected-text, falling back
  to synthesizing Cmd-C + reading the pasteboard (then restoring it) for apps
  that don't expose AX. Speaks via Speaker with --normalize; re-pressing (or
  pressing with nothing selected) cancels the current read.
- Hotkey: parameterize the hotkey id so a second global hotkey can register,
  and gate each handler on its own id (handlers on the app target see all
  hotkey events).
- Speaker: add a --normalize option and cancel() (talks to the daemon socket
  via nc, so it works regardless of installed CLI version; no-op if the daemon
  predates `cancel`).
- Config: STACKNUDGE_SPEAK_HOTKEY.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The id-gating handler returned noErr even on a non-matching id. In Carbon that
marks the event handled and stops chain propagation, so with two hotkeys
registered whichever handler ran first consumed the event and the other hotkey
(e.g. ⌘⌥N to open the panel) stopped firing. Return eventNotHandledErr on a
non-match so the event reaches the handler whose id matches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface and configure the speak-selection hotkey in Settings, mirroring the
panel-hotkey recorder: a "Read aloud shortcut" row in the Voice section shows
the current binding and records a new one (persists STACKNUDGE_SPEAK_HOTKEY,
re-registers via setSpeakHotkey).

- SettingsRow.speakHotkey + placement in the Voice group.
- Recorder state (recordingSpeakHotkey / speakHotkeyDisplay / speakHotkeyError)
  and start/cancel/commit, mirroring the panel hotkey.
- Panel key-capture routes the recorded combo to whichever hotkey is recording;
  registerSpeakHotkey re-registers the id:2 global hotkey.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@StuBehan
StuBehan force-pushed the feat/speak-selection-hotkey branch from 21ef58e to b19e9bc Compare July 13, 2026 17:49
@StuBehan
StuBehan merged commit 9d7da6a into main Jul 13, 2026
6 checks passed
@StuBehan
StuBehan deleted the feat/speak-selection-hotkey branch July 13, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant