From 623928812d250c00fa6ee5249dd6bf8606d4a5af Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Sun, 22 Mar 2026 22:31:59 +0100 Subject: [PATCH] fix: always enable web fallback keybind listeners alongside Electron IPC When the Electron main process can't start uiohook (macOS Accessibility denied, Linux missing input group), the IPC path silently produces no events. Previously this left zero keybind listeners active. Now the web capture-phase fallback is always set up as a safety net, ensuring keybinds work in-app even when the OS-level hook fails. --- packages/web/src/hooks/useKeybinds.ts | 43 +++++++++++++++------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/web/src/hooks/useKeybinds.ts b/packages/web/src/hooks/useKeybinds.ts index 7d4fc527..40b818a0 100644 --- a/packages/web/src/hooks/useKeybinds.ts +++ b/packages/web/src/hooks/useKeybinds.ts @@ -198,31 +198,36 @@ export function useKeybinds(): void { } }, [keybinds, currentVoiceChannelId]); - // --- Keybind listeners (Electron IPC or web capture-phase fallback) --- + // --- Electron IPC bridge (global shortcuts via uiohook in main process) --- useEffect(() => { if (keybinds.length === 0) return; + if (!isElectron()) return; + const api = window.backspace; + if (!api?.syncKeybinds || !api?.onKeybindAction) return; - // Try Electron IPC bridge first (requires rebuilt desktop app with keybind support) - if (isElectron()) { - const api = window.backspace; - if (api?.syncKeybinds && api?.onKeybindAction) { - // Desktop app has keybind support — use OS-level global shortcuts - api.syncKeybinds(keybinds.map((kb) => ({ - actionId: kb.actionId, - keys: kb.keys, - mouseButton: kb.mouseButton, - }))); + // Sync keybind config to main process — it registers OS-level hooks + api.syncKeybinds(keybinds.map((kb) => ({ + actionId: kb.actionId, + keys: kb.keys, + mouseButton: kb.mouseButton, + }))); - const cleanup = api.onKeybindAction((action) => { - dispatchKeybindAction(action.actionId, action.pressed); - }); + // Listen for matched actions from main process + const cleanup = api.onKeybindAction((action) => { + dispatchKeybindAction(action.actionId, action.pressed); + }); - return cleanup; - } - // Desktop app lacks keybind APIs (old build) — fall through to web fallback - } + return cleanup; + }, [keybinds]); - // Web fallback: capture-phase listeners (works when tab/window is focused) + // --- Web fallback: always active as safety net --- + // On web: this is the only keybind path (works when tab is focused). + // On Electron: this provides in-app keybinds even if the OS-level hook + // fails to start (e.g. macOS Accessibility permission denied). When the + // global hook IS working, both fire but dispatchKeybindAction is + // idempotent for toggles (no-op if not in voice / already handled). + useEffect(() => { + if (keybinds.length === 0) return; const cleanup = setupWebFallback(keybindsRef); return cleanup ?? undefined; }, [keybinds]);