[eric] voice: one-time Globe-key conflict tip when macOS still binds fn to the emoji picker

This commit is contained in:
ciregenz
2026-08-05 21:15:55 -07:00
parent 85659c71e7
commit 2dd10e164a
4 changed files with 35 additions and 2 deletions
+6
View File
@@ -116,6 +116,12 @@ contextBridge.exposeInMainWorld('openswarm', {
ipcRenderer.on('voice:hold-up', up);
return () => { ipcRenderer.removeListener('voice:hold-down', down); ipcRenderer.removeListener('voice:hold-up', up); };
},
// Fires once at fn-watcher arm when macOS's own Globe-key action is still active (emoji picker on tap).
onVoiceGlobeConflict: (cb) => {
const h = () => cb();
ipcRenderer.on('voice:globe-conflict', h);
return () => ipcRenderer.removeListener('voice:globe-conflict', h);
},
// Hands a vetted social platform's partition cookies to its session-backed MCP shim (allowlisted domains only, gated again in the main process).
getPartitionCookies: (domain) => ipcRenderer.invoke('get-partition-cookies', domain),
// Silently reads the user's own chatgpt.com/claude.ai history offscreen (no card) for onboarding personalization; main owns the injected script + gates the provider.
+10
View File
@@ -169,6 +169,16 @@ function installVoiceHotkey(getMainWindow) {
});
app.on('will-quit', () => { try { fnProc && fnProc.kill('SIGKILL'); } catch (_) {} });
console.log('[voice] fn watcher armed (awaiting first event to prove Input Monitoring)');
// macOS's own Globe-key action (emoji picker by default) fires on a quick fn tap alongside us;
// tell the renderer once so it can point the user at "Press Globe key to: Do Nothing".
require('child_process').exec('defaults read com.apple.HIToolbox AppleFnUsageType', (err, out) => {
const usage = err ? '2' : String(out).trim();
if (usage !== '0') {
const win = getMainWindow();
if (win && !win.isDestroyed()) win.webContents.send('voice:globe-conflict');
console.log(`[voice] Globe key system action is active (AppleFnUsageType=${usage}); quick fn taps also trigger it`);
}
});
};
let tapKeycode;
@@ -10,7 +10,19 @@ import VoiceOverlay from './VoiceOverlay';
// out-of-sync state. Mounted once near the app root.
export function VoiceDictationProvider({ children }: { children: React.ReactNode }): React.ReactElement {
const { state, lastText, error, pct, feedback, partial, toggle, start, stop, cancel, volumeRef } = useVoiceDictation();
const { state, lastText, error, pct, feedback, partial, toggle, start, stop, cancel, notify, volumeRef } = useVoiceDictation();
// fn is the dictation key, but macOS may still have its own Globe action bound (emoji picker on a quick tap); say so once.
const globeWarnedRef = useRef(false);
useEffect(() => {
const bridge = window as unknown as { openswarm?: { onVoiceGlobeConflict?: (cb: () => void) => () => void } };
const off = bridge.openswarm?.onVoiceGlobeConflict?.(() => {
if (globeWarnedRef.current) return;
globeWarnedRef.current = true;
notify('Tip: set System Settings > Keyboard > "Press Globe key to" to "Do Nothing" so fn only dictates.');
});
return () => { off?.(); };
}, [notify]);
const holdMode = useAppSelector((s) => s.settings.data.voice_hold_to_talk ?? true);
const dictationShortcut = useAppSelector((s) => s.settings.data.dictation_shortcut ?? null);
const dictationModel = useAppSelector((s) => s.settings.data.dictation_model ?? null);
@@ -294,5 +294,10 @@ export function useVoiceDictation() {
// A dangling recorder (unmount mid-capture) must release the mic.
useEffect(() => () => { teardown(); }, [teardown]);
return { state, lastText, error, pct, feedback, partial, toggle, start, stop, cancel, volumeRef };
// One-line notices from outside the session flow (Globe-key conflict, permission hints) ride the same bubble.
const notify = useCallback((text: string): void => {
setFeedback({ tone: 'warn', icon: 'info', text, at: Date.now() });
}, []);
return { state, lastText, error, pct, feedback, partial, toggle, start, stop, cancel, notify, volumeRef };
}