diff --git a/src/core/services/shortcut-fallback-service.ts b/src/core/services/shortcut-fallback-service.ts new file mode 100644 index 0000000..898b697 --- /dev/null +++ b/src/core/services/shortcut-fallback-service.ts @@ -0,0 +1,89 @@ +import { globalShortcut } from "electron"; + +export function isGlobalShortcutRegisteredSafe(accelerator: string): boolean { + try { + return globalShortcut.isRegistered(accelerator); + } catch { + return false; + } +} + +export function shortcutMatchesInputForLocalFallback( + input: Electron.Input, + accelerator: string, + allowWhenRegistered = false, +): boolean { + if (input.type !== "keyDown" || input.isAutoRepeat) return false; + if (!accelerator) return false; + if (!allowWhenRegistered && isGlobalShortcutRegisteredSafe(accelerator)) { + return false; + } + + const normalized = accelerator + .replace(/\s+/g, "") + .replace(/cmdorctrl/gi, "CommandOrControl") + .toLowerCase(); + const parts = normalized.split("+").filter(Boolean); + if (parts.length === 0) return false; + + const keyToken = parts[parts.length - 1]; + const modifierTokens = new Set(parts.slice(0, -1)); + const allowedModifiers = new Set([ + "shift", + "alt", + "meta", + "control", + "commandorcontrol", + ]); + for (const token of modifierTokens) { + if (!allowedModifiers.has(token)) return false; + } + + const inputKey = (input.key || "").toLowerCase(); + if (keyToken.length === 1) { + if (inputKey !== keyToken) return false; + } else if (keyToken.startsWith("key") && keyToken.length === 4) { + if (inputKey !== keyToken.slice(3)) return false; + } else { + return false; + } + + const expectedShift = modifierTokens.has("shift"); + const expectedAlt = modifierTokens.has("alt"); + const expectedMeta = modifierTokens.has("meta"); + const expectedControl = modifierTokens.has("control"); + const expectedCommandOrControl = modifierTokens.has("commandorcontrol"); + + if (Boolean(input.shift) !== expectedShift) return false; + if (Boolean(input.alt) !== expectedAlt) return false; + + if (expectedCommandOrControl) { + const hasCmdOrCtrl = + process.platform === "darwin" + ? Boolean(input.meta || input.control) + : Boolean(input.control); + if (!hasCmdOrCtrl) return false; + } else { + if (process.platform === "darwin") { + if (input.meta || input.control) return false; + } else if (input.control) { + return false; + } + } + + if (expectedMeta && !input.meta) return false; + if (!expectedMeta && modifierTokens.has("meta") === false && input.meta) { + if (!expectedCommandOrControl) return false; + } + + if (expectedControl && !input.control) return false; + if ( + !expectedControl && + modifierTokens.has("control") === false && + input.control + ) { + if (!expectedCommandOrControl) return false; + } + + return true; +} diff --git a/src/main.ts b/src/main.ts index 0617c9e..adda9be 100644 --- a/src/main.ts +++ b/src/main.ts @@ -131,6 +131,10 @@ import { } from "./core/services/subtitle-ws-service"; import { registerGlobalShortcutsService } from "./core/services/shortcut-service"; import { registerIpcHandlersService } from "./core/services/ipc-service"; +import { + isGlobalShortcutRegisteredSafe, + shortcutMatchesInputForLocalFallback, +} from "./core/services/shortcut-fallback-service"; import { ConfigService, DEFAULT_CONFIG, @@ -2405,142 +2409,6 @@ function getConfiguredShortcuts() { }; } -function shouldUseMarkAudioCardLocalFallback(input: Electron.Input): boolean { - const shortcuts = getConfiguredShortcuts(); - if (!shortcuts.markAudioCard) return false; - if (globalShortcut.isRegistered(shortcuts.markAudioCard)) return false; - - const normalized = shortcuts.markAudioCard.replace(/\s+/g, "").toLowerCase(); - const supportsFallback = - normalized === "commandorcontrol+shift+a" || - normalized === "cmdorctrl+shift+a" || - normalized === "control+shift+a" || - normalized === "ctrl+shift+a"; - if (!supportsFallback) return false; - - if (input.type !== "keyDown" || input.isAutoRepeat) return false; - if ((input.key || "").toLowerCase() !== "a") return false; - if (!input.shift || input.alt) return false; - - if (process.platform === "darwin") { - return Boolean(input.meta || input.control); - } - return Boolean(input.control); -} - -function shouldUseRuntimeOptionsLocalFallback(input: Electron.Input): boolean { - const shortcuts = getConfiguredShortcuts(); - if (!shortcuts.openRuntimeOptions) return false; - if (globalShortcut.isRegistered(shortcuts.openRuntimeOptions)) return false; - - const normalized = shortcuts.openRuntimeOptions - .replace(/\s+/g, "") - .toLowerCase(); - const supportsFallback = - normalized === "commandorcontrol+shift+o" || - normalized === "cmdorctrl+shift+o" || - normalized === "control+shift+o" || - normalized === "ctrl+shift+o"; - if (!supportsFallback) return false; - - if (input.type !== "keyDown" || input.isAutoRepeat) return false; - if ((input.key || "").toLowerCase() !== "o") return false; - if (!input.shift || input.alt) return false; - - if (process.platform === "darwin") { - return Boolean(input.meta || input.control); - } - return Boolean(input.control); -} - -function isGlobalShortcutRegisteredSafe(accelerator: string): boolean { - try { - return globalShortcut.isRegistered(accelerator); - } catch { - return false; - } -} - -function shortcutMatchesInputForLocalFallback( - input: Electron.Input, - accelerator: string, - allowWhenRegistered = false, -): boolean { - if (input.type !== "keyDown" || input.isAutoRepeat) return false; - if (!accelerator) return false; - if (!allowWhenRegistered && isGlobalShortcutRegisteredSafe(accelerator)) { - return false; - } - - const normalized = accelerator - .replace(/\s+/g, "") - .replace(/cmdorctrl/gi, "CommandOrControl") - .toLowerCase(); - const parts = normalized.split("+").filter(Boolean); - if (parts.length === 0) return false; - - const keyToken = parts[parts.length - 1]; - const modifierTokens = new Set(parts.slice(0, -1)); - const allowedModifiers = new Set([ - "shift", - "alt", - "meta", - "control", - "commandorcontrol", - ]); - for (const token of modifierTokens) { - if (!allowedModifiers.has(token)) return false; - } - - const inputKey = (input.key || "").toLowerCase(); - if (keyToken.length === 1) { - if (inputKey !== keyToken) return false; - } else if (keyToken.startsWith("key") && keyToken.length === 4) { - if (inputKey !== keyToken.slice(3)) return false; - } else { - return false; - } - - const expectedShift = modifierTokens.has("shift"); - const expectedAlt = modifierTokens.has("alt"); - const expectedMeta = modifierTokens.has("meta"); - const expectedControl = modifierTokens.has("control"); - const expectedCommandOrControl = modifierTokens.has("commandorcontrol"); - - if (Boolean(input.shift) !== expectedShift) return false; - if (Boolean(input.alt) !== expectedAlt) return false; - - if (expectedCommandOrControl) { - const hasCmdOrCtrl = - process.platform === "darwin" - ? Boolean(input.meta || input.control) - : Boolean(input.control); - if (!hasCmdOrCtrl) return false; - } else { - if (process.platform === "darwin") { - if (input.meta || input.control) return false; - } else if (input.control) { - return false; - } - } - - if (expectedMeta && !input.meta) return false; - if (!expectedMeta && modifierTokens.has("meta") === false && input.meta) { - if (!expectedCommandOrControl) return false; - } - - if (expectedControl && !input.control) return false; - if ( - !expectedControl && - modifierTokens.has("control") === false && - input.control - ) { - if (!expectedCommandOrControl) return false; - } - - return true; -} - function tryHandleOverlayShortcutLocalFallback(input: Electron.Input): boolean { const shortcuts = getConfiguredShortcuts(); const handlers: Array<{