Summary
On macOS the fn key cannot be bound to a capture chord, even though every other layer of the stack already supports it. Worse than a no-op: a chord containing Function makes build_chord_bindings return Err, so the HotkeyMonitor is never spawned and both push-to-talk and toggle-to-talk stop working — global dictation is silently dead until the chord is changed back.
Root cause
tauri/src-tauri/src/key_codes.rs::key_from_str() has no "Function" arm, so it falls through to _ => return None.
main.rs:891 treats that as fatal:
let key = key_codes::key_from_str(raw)
.ok_or_else(|| format!("Unsupported key in {name} chord: {raw}"))?;
The ? propagates out of build_chord, build_chord_bindings returns Err, and HotkeyMonitor::apply is never reached — so there's no ChordMatcher build failed line on stderr either, which makes this hard to diagnose from logs.
Every other layer already handles fn
- keytap 0.4 —
src/platform/macos/keycodes.rs: const FUNCTION: u32 = 63; // kVK_Function and FUNCTION => Key::Function. The tap observes the key.
- keytap —
Key::Function exists (src/key.rs).
- frontend —
app/src/lib/utils/keyCodes.ts: 'Function' is in the passthrough allowlist of canonicalKeyFromEvent, and displayLabelForKey maps 'Function' → 'fn', which reads as an intent to support it.
Only the string→Key bridge drops it.
Proposed fix
One arm in key_codes.rs, next to the other modifiers:
"Function" => Key::Function,
Happy to open a PR if useful.
Repro
macOS 26.5.2 (build 25F84), Apple M1, Voicebox 0.5.0 (prebuilt aarch64 DMG), MLX backend.
- Quit Voicebox.
sqlite3 "$HOME/Library/Application Support/sh.voicebox.app/voicebox.db" "UPDATE capture_settings SET chord_push_to_talk_keys='[\"Function\"]' WHERE id=1;"
- Relaunch.
Result: no chord fires at all — neither the fn chord nor the untouched toggle chord. Reverting chord_push_to_talk_keys to a mapped key (e.g. ["MetaRight"]) restores hotkeys immediately.
I set the value through the DB rather than the chord picker, so I can't say from this repro whether the picker itself can capture fn on macOS — WebKit may not deliver a keydown for it, in which case the picker would need to source the key from the native tap for fn to be selectable in the UI even after the bridge is fixed.
Why it matters
fn is the dictation key people coming from Wispr Flow expect, and it's the one key on a Mac keyboard with no competing role in most apps.
Summary
On macOS the
fnkey cannot be bound to a capture chord, even though every other layer of the stack already supports it. Worse than a no-op: a chord containingFunctionmakesbuild_chord_bindingsreturnErr, so theHotkeyMonitoris never spawned and both push-to-talk and toggle-to-talk stop working — global dictation is silently dead until the chord is changed back.Root cause
tauri/src-tauri/src/key_codes.rs::key_from_str()has no"Function"arm, so it falls through to_ => return None.main.rs:891treats that as fatal:The
?propagates out ofbuild_chord,build_chord_bindingsreturnsErr, andHotkeyMonitor::applyis never reached — so there's noChordMatcher build failedline on stderr either, which makes this hard to diagnose from logs.Every other layer already handles fn
src/platform/macos/keycodes.rs:const FUNCTION: u32 = 63; // kVK_FunctionandFUNCTION => Key::Function. The tap observes the key.Key::Functionexists (src/key.rs).app/src/lib/utils/keyCodes.ts:'Function'is in the passthrough allowlist ofcanonicalKeyFromEvent, anddisplayLabelForKeymaps'Function'→'fn', which reads as an intent to support it.Only the string→
Keybridge drops it.Proposed fix
One arm in
key_codes.rs, next to the other modifiers:Happy to open a PR if useful.
Repro
macOS 26.5.2 (build 25F84), Apple M1, Voicebox 0.5.0 (prebuilt aarch64 DMG), MLX backend.
sqlite3 "$HOME/Library/Application Support/sh.voicebox.app/voicebox.db" "UPDATE capture_settings SET chord_push_to_talk_keys='[\"Function\"]' WHERE id=1;"Result: no chord fires at all — neither the fn chord nor the untouched toggle chord. Reverting
chord_push_to_talk_keysto a mapped key (e.g.["MetaRight"]) restores hotkeys immediately.I set the value through the DB rather than the chord picker, so I can't say from this repro whether the picker itself can capture
fnon macOS — WebKit may not deliver a keydown for it, in which case the picker would need to source the key from the native tap for fn to be selectable in the UI even after the bridge is fixed.Why it matters
fnis the dictation key people coming from Wispr Flow expect, and it's the one key on a Mac keyboard with no competing role in most apps.