diff --git a/electron/main.js b/electron/main.js
index 847035a6..fab4460c 100644
--- a/electron/main.js
+++ b/electron/main.js
@@ -1793,18 +1793,22 @@ app.whenReady().then(async () => {
// focused, MAIN watches before-input-event (sees every keyDown/keyUp incl. modifiers, regardless of
// which element or webview has focus, and immune to macOS's letter-keyup-under-Cmd suppression at the
// DOM layer) and relays voice:hold-down / voice:hold-up; the renderer maps those to hold-vs-toggle.
- const VOICE_COMBO = 'CommandOrControl+Shift+D';
+ // F5 is the mic/dictation key printed on Mac keyboards (the fn/globe key itself is invisible to
+ // Electron without a native event tap), so it's the simple primary; the old combo stays as backup.
+ const VOICE_COMBOS = ['F5', 'CommandOrControl+Shift+D'];
const registerVoiceShortcut = () => {
- try {
- if (!globalShortcut.isRegistered(VOICE_COMBO)) {
- globalShortcut.register(VOICE_COMBO, () => {
- if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('voice:toggle');
- });
- }
- } catch (_) { /* a taken shortcut just means no global hotkey; the pill still works */ }
+ for (const combo of VOICE_COMBOS) {
+ try {
+ if (!globalShortcut.isRegistered(combo)) {
+ globalShortcut.register(combo, () => {
+ if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('voice:toggle');
+ });
+ }
+ } catch (_) { /* a taken shortcut just means no global hotkey; the pill still works */ }
+ }
};
registerVoiceShortcut();
- app.on('browser-window-focus', () => { try { globalShortcut.unregister(VOICE_COMBO); } catch (_) {} });
+ app.on('browser-window-focus', () => { for (const combo of VOICE_COMBOS) { try { globalShortcut.unregister(combo); } catch (_) {} } });
app.on('browser-window-blur', registerVoiceShortcut);
// Every discrete combo press relays (autorepeat filtered); the renderer toggles record state.
@@ -1813,8 +1817,10 @@ app.whenReady().then(async () => {
// without a native event tap. Keyboard = press to start/stop; the mic buttons own true hold-to-talk.
const installVoiceHoldRelay = (contents) => {
contents.on('before-input-event', (event, input) => {
+ if (input.type !== 'keyDown' || input.isAutoRepeat) return;
const isD = (input.code === 'KeyD' || (input.key || '').toLowerCase() === 'd');
- if (input.type === 'keyDown' && isD && (input.meta || input.control) && input.shift && !input.isAutoRepeat) {
+ const combo = (isD && (input.meta || input.control) && input.shift) || input.code === 'F5';
+ if (combo) {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('voice:hold-down');
event.preventDefault();
}
diff --git a/frontend/src/app/pages/Dashboard/desktop/DesktopSpawnPill.tsx b/frontend/src/app/pages/Dashboard/desktop/DesktopSpawnPill.tsx
index 664bfb4b..f6afb222 100644
--- a/frontend/src/app/pages/Dashboard/desktop/DesktopSpawnPill.tsx
+++ b/frontend/src/app/pages/Dashboard/desktop/DesktopSpawnPill.tsx
@@ -160,7 +160,7 @@ function DesktopSpawnPill({
>
-
+
Dictation
- How the mic button works. Cmd+Shift+D always starts and stops.
+ How the mic button works. The mic key (F5) starts and stops.
): void {
- const ac = audio();
- if (!ac) return;
- const t0 = ac.currentTime;
- for (const [freq, at, dur] of freqs) {
- const osc = ac.createOscillator();
- const gain = ac.createGain();
- osc.type = 'sine';
- osc.frequency.value = freq;
- gain.gain.setValueAtTime(0, t0 + at);
- gain.gain.linearRampToValueAtTime(0.055, t0 + at + 0.02);
- gain.gain.exponentialRampToValueAtTime(0.0001, t0 + at + dur);
- osc.connect(gain).connect(ac.destination);
- osc.start(t0 + at);
- osc.stop(t0 + at + dur + 0.05);
- }
-}
-
-export function playStart(): void { blip([[523, 0, 0.12], [784, 0.09, 0.16]]); }
-export function playCancel(): void { blip([[784, 0, 0.1], [523, 0.08, 0.14]]); }
-export function playDone(): void { blip([[1047, 0, 0.09]]); }