Skip to content

feat(whisper): add dropdown preset keybinding selector for speech-to-text#447

Open
monotykamary wants to merge 7 commits into
SuperCmdLabs:mainfrom
monotykamary:feat/whisper-hotkey-dropdown
Open

feat(whisper): add dropdown preset keybinding selector for speech-to-text#447
monotykamary wants to merge 7 commits into
SuperCmdLabs:mainfrom
monotykamary:feat/whisper-hotkey-dropdown

Conversation

@monotykamary
Copy link
Copy Markdown
Contributor

@monotykamary monotykamary commented May 26, 2026

Summary

There was no easy way to change the keybinding for speech-to-text beyond the default Fn key. The HotkeyRecorder component cannot capture standalone modifier keys (Option, Command, etc.) because browser keydown events treat them as modifiers rather than keys, making them impossible to record.

This PR adds a dropdown with preset keybinding options in the Whisper hotkey settings, with full backend support for standalone modifier keys as hold-to-talk triggers.

image image image

Changes

UI — src/renderer/src/settings/AITab.tsx

  • Added a <select> dropdown with 5 hold-to-talk preset options:
    • fn (hold-to-talk, default)
    • ⌥ Left Option (hold-to-talk)
    • ⌥ Right Option (hold-to-talk)
    • ⌘ Left Command (hold-to-talk)
    • ⌘ Right Command (hold-to-talk)
  • A "Custom…" option reveals the HotkeyRecorder for recording any key combo
  • Replaced the old HotkeyRecorder + "Set to Fn" button UI
  • Added always-visible hint text explaining the hold-to-talk behavior

UI — src/renderer/src/settings/HotkeyRecorder.tsx

  • Added autoRecord prop: when set, the recorder auto-starts in recording mode and grabs focus on mount
  • Fixes the "Custom…" flow — previously the <select> retained focus and intercepted keyboard events, preventing the recorder from receiving input

Display — src/renderer/src/utils/hyper-key.ts

  • Added directional display for left/right modifier variants: ⌥←, ⌥→, ⌘←, ⌘→
  • Added these to the modifier symbols set so they render as modifiers (not keys)

Backend — src/main/main.ts

  • Added STANDALONE_MODIFIER_KEYCODES map with both left and right modifier key codes (alt→58, leftoption→58, rightoption→61, command→55, leftcommand→55, rightcommand→54, control→59, shift→56 — macOS virtual key codes)
  • Added isStandaloneModifierShortcut() and needsNativeHoldWatcher() helper functions
  • Extended parseHoldShortcutConfig() key code map with left/right modifier keys, setting the corresponding modifier flag when the key token is a standalone modifier (matching the existing Fn pattern)
  • Added fnSpeakToggleCurrentShortcut variable (was previously hardcoded to 'Fn'), so the watcher adapts to whichever key the user selects
  • Generalized syncFnSpeakToggleWatcher() to activate for any shortcut needing a native watcher (Fn, Alt/Option, left or right) and properly restart when the shortcut changes
  • Updated registerCommandHotkeys() and updateCommandHotkey IPC handler to skip Electron globalShortcut for standalone modifier shortcuts on the speak-toggle command (handled by native CGEventTap instead)
  • Updated normalizeAccelerator() to recognize left/right modifier tokens as modifiers when used in combos (e.g. LeftOption+SpaceAlt+Space)
  • Standalone modifier shortcuts are rejected for non-speak-toggle commands with "unavailable" error

No native code changes needed

The hotkey-hold-monitor.swift binary already handles flagsChanged events for modifier keys and accepts any key code + modifier combination via CLI args.

Locales — all 9 locale files

  • Removed: setToFn, fnHint, fnHintBefore, fnHintAfter
  • Added: custom ("Custom…"), holdToTalkHint (explanatory help text)
  • All locales verified in structural parity with en.json

How to Test

  1. Open Settings → AI → Whisper
  2. Under Whisper Hotkeys, verify the dropdown appears with 5 preset options + Custom
  3. Select ⌥ Left Option — the watcher should start monitoring the left Option key
  4. Hold left Option to start dictation, release to stop — verify it works like Fn
  5. Select ⌘ Right Command — the watcher should start monitoring the right Command key
  6. Hold right Command to start dictation, release to stop
  7. Select Custom… — the HotkeyRecorder should auto-start recording; press a key combo
  8. Verify the whisper overlay shortcut hint label updates correctly (shows ⌥←, ⌥→, ⌘←, ⌘→ for left/right variants)
  9. Verify selecting a standalone modifier for a non-whisper command shows "hotkey unavailable"

…text

Replace the single HotkeyRecorder + 'Set to Fn' button in Whisper hotkey
settings with a dropdown offering preset keybinding options. This solves
the problem where standalone modifier keys (Option, Command, etc.) could
not be captured by the recorder since browser keydown events treat them
as modifiers rather than keys.

Preset options include: Fn (hold-to-talk), Option (hold-to-talk), F5,
F6, Option+Space, Option+., Command+., Control+Space, Shift+Space.
A 'Custom…' option in the dropdown reveals the HotkeyRecorder for
advanced key combos.

Backend changes generalize the native CGEventTap-based Fn speak-toggle
watcher to support any standalone modifier key as a hold-to-talk trigger
(Option/Alt keyCode 58, Command keyCode 55, etc.). The
hotkey-hold-monitor.swift binary already handles flagsChanged events for
modifier keys, so no native code changes were needed.

All 9 locale files updated with new i18n keys (custom, holdToTalkHint)
replacing the removed keys (setToFn, fnHint, fnHintBefore, fnHintAfter).
@monotykamary monotykamary marked this pull request as draft May 26, 2026 01:26
…ix Custom auto-record

- Replace the 9 preset list (F5, F6, combos) with the 3 hold-to-talk
  modifier families: Fn, Left/Right Option, Left/Right Command
- Add right-side modifier key codes (61=RightOption, 54=RightCommand) to
  STANDALONE_MODIFIER_KEYCODES and parseHoldShortcutConfig key map
- Update normalizeAccelerator to recognize left/right modifier tokens as
  modifiers when used in combos (e.g. LeftOption+Space → Alt+Space)
- Update isStandaloneAlt/isStandaloneCmd detection for left/right variants
- Format display: ⌥←/⌥→/⌘←/⌘→ with directional arrows for side info
- Fix Custom option: blur the <select> when Custom is chosen so it stops
  intercepting keyboard events, and add autoRecord prop to HotkeyRecorder
  so it auto-starts recording and grabs focus on mount
…ix hooks order

The useState for whisperCustomMode was placed after the early return
(!settings), causing React to throw 'Rendered more hooks than during the
previous render' when settings loaded asynchronously.
The recorder visibility was gated on whisperPresetValue which only
reflects the stored hotkey — but selecting 'Custom…' doesn't change the
stored value, so the recorder never appeared. Use whisperSelectValue
instead, which accounts for the user's explicit Custom selection.
@monotykamary monotykamary marked this pull request as ready for review May 26, 2026 01:41
Keep whisperCustomMode=true until the stored hotkey actually updates
via IPC instead of clearing it in the recorder's onChange (which fires
before the async settings save completes). A 150ms timeout fallback
handles the edge case where the recorded value equals the current
stored value.
The useEffect for clearing whisperCustomMode was placed after the
component-level early return guard, causing the hooks order mismatch
again. Derive whisperSpeakToggleHotkey from settings? before the guard
and move the effect up with the other hooks.
@monotykamary monotykamary marked this pull request as draft May 26, 2026 01:44
The previous 150ms timeout-based useEffect fired immediately when
custom mode was entered, snapping the dropdown back before the user
could record anything. Now custom mode only clears when the
HotkeyRecorder's onChange resolves (i.e. after IPC save completes).
@monotykamary monotykamary marked this pull request as ready for review May 26, 2026 01:51
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