From cb9a599b2359920f7e78bea2112390bd08f3b5ca Mon Sep 17 00:00:00 2001 From: sudacode Date: Sat, 14 Feb 2026 00:35:30 -0800 Subject: [PATCH] Fix overlay modal dispatch and restore behavior --- .gitmodules | 4 -- src/main.ts | 159 ++++++++++++++++++++++++++++++++++++++++++++++--- vendor/ani-cli | 1 - 3 files changed, 151 insertions(+), 13 deletions(-) delete mode 160000 vendor/ani-cli diff --git a/.gitmodules b/.gitmodules index 60ca904..31ab7ff 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,7 +2,3 @@ path = vendor/texthooker-ui url = https://github.com/ksyasuda/texthooker-ui.git branch = subminer - -[submodule "vendor/ani-cli"] - path = vendor/ani-cli - url = https://github.com/pystardust/ani-cli diff --git a/src/main.ts b/src/main.ts index 24a45a7..13731ff 100644 --- a/src/main.ts +++ b/src/main.ts @@ -176,6 +176,12 @@ if (process.platform === "linux") { } const DEFAULT_TEXTHOOKER_PORT = 5174; +const DEFAULT_MPV_LOG_FILE = path.join( + os.homedir(), + ".cache", + "SubMiner", + "mp.log", +); function resolveConfigDir(): string { const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim(); const baseDirs = Array.from( @@ -212,6 +218,7 @@ function resolveConfigDir(): string { const CONFIG_DIR = resolveConfigDir(); const USER_DATA_PATH = CONFIG_DIR; +const DEFAULT_MPV_LOG_PATH = process.env.SUBMINER_MPV_LOG?.trim() || DEFAULT_MPV_LOG_FILE; const configService = new ConfigService(CONFIG_DIR); const isDev = process.argv.includes("--dev") || process.argv.includes("--debug"); @@ -270,6 +277,7 @@ let currentSubAssText = ""; let windowTracker: BaseWindowTracker | null = null; let subtitlePosition: SubtitlePosition | null = null; let currentMediaPath: string | null = null; +let currentMediaTitle: string | null = null; let pendingSubtitlePosition: SubtitlePosition | null = null; let mecabTokenizer: MecabTokenizer | null = null; let keybindings: Keybinding[] = []; @@ -297,8 +305,10 @@ const overlayContentMeasurementStore = createOverlayContentMeasurementStoreServi console.warn(message); }, }); -type OverlayHostedModal = "runtime-options" | "subsync"; +type OverlayHostedModal = "runtime-options" | "subsync" | "jimaku"; +type OverlayHostLayer = "visible" | "invisible"; const restoreVisibleOverlayOnModalClose = new Set(); +const overlayModalAutoShownLayer = new Map(); function getFieldGroupingResolver(): ((choice: KikuFieldGroupingChoice) => void) | null { return fieldGroupingResolver; @@ -328,8 +338,11 @@ const fieldGroupingOverlayRuntime = createFieldGroupingOverlayRuntimeService getFieldGroupingResolver(), setResolver: (resolver) => setFieldGroupingResolver(resolver), getRestoreVisibleOverlayOnModalClose: () => restoreVisibleOverlayOnModalClose, + sendToVisibleOverlay: (channel, payload) => { + sendToActiveOverlayWindow(channel, payload); + return true; + }, }); -const sendToVisibleOverlay = fieldGroupingOverlayRuntime.sendToVisibleOverlay; const createFieldGroupingCallback = fieldGroupingOverlayRuntime.createFieldGroupingCallback; @@ -357,6 +370,79 @@ function broadcastRuntimeOptionsChanged(): void { ); } +function getTargetOverlayWindow(): { + window: BrowserWindow; + layer: OverlayHostLayer; +} | null { + const visibleMainWindow = overlayManager.getMainWindow(); + const invisibleWindow = overlayManager.getInvisibleWindow(); + + if (visibleMainWindow && !visibleMainWindow.isDestroyed()) { + return { window: visibleMainWindow, layer: "visible" }; + } + + if (invisibleWindow && !invisibleWindow.isDestroyed()) { + return { window: invisibleWindow, layer: "invisible" }; + } + + return null; +} + +function showOverlayWindowForModal(window: BrowserWindow, layer: OverlayHostLayer): void { + if (layer === "invisible" && typeof window.showInactive === "function") { + window.showInactive(); + } else { + window.show(); + } + if (!window.isFocused()) { + window.focus(); + } +} + +function sendToActiveOverlayWindow( + channel: string, + payload?: unknown, + runtimeOptions?: { restoreOnModalClose?: OverlayHostedModal }, +): void { + const target = getTargetOverlayWindow(); + if (!target) return; + + const { window: targetWindow, layer } = target; + const wasVisible = targetWindow.isVisible(); + const restoreOnModalClose = runtimeOptions?.restoreOnModalClose; + + const sendNow = (): void => { + if (payload === undefined) { + targetWindow.webContents.send(channel); + } else { + targetWindow.webContents.send(channel, payload); + } + }; + + if (!wasVisible) { + showOverlayWindowForModal(targetWindow, layer); + } + if (!wasVisible && restoreOnModalClose) { + restoreVisibleOverlayOnModalClose.add(restoreOnModalClose); + overlayModalAutoShownLayer.set(restoreOnModalClose, layer); + } + + if (targetWindow.webContents.isLoading()) { + targetWindow.webContents.once("did-finish-load", () => { + if ( + targetWindow && + !targetWindow.isDestroyed() && + !targetWindow.webContents.isLoading() + ) { + sendNow(); + } + }); + return; + } + + sendNow(); +} + function setOverlayDebugVisualizationEnabled(enabled: boolean): void { setOverlayDebugVisualizationEnabledRuntimeService( overlayDebugVisualizationEnabled, @@ -368,7 +454,11 @@ function setOverlayDebugVisualizationEnabled(enabled: boolean): void { ); } -function openRuntimeOptionsPalette(): void { sendToVisibleOverlay("runtime-options:open", undefined, { restoreOnModalClose: "runtime-options" }); } +function openRuntimeOptionsPalette(): void { + sendToActiveOverlayWindow("runtime-options:open", undefined, { + restoreOnModalClose: "runtime-options", + }); +} function getResolvedConfig() { return configService.getConfig(); } @@ -437,6 +527,9 @@ function saveSubtitlePosition(position: SubtitlePosition): void { } function updateCurrentMediaPath(mediaPath: unknown): void { + if (typeof mediaPath !== "string" || !isRemoteMediaPath(mediaPath)) { + currentMediaTitle = null; + } updateCurrentMediaPathService({ mediaPath, currentMediaPath, @@ -458,6 +551,21 @@ function updateCurrentMediaPath(mediaPath: unknown): void { }); } +function updateCurrentMediaTitle(mediaTitle: unknown): void { + if (typeof mediaTitle === "string") { + const sanitized = mediaTitle.trim(); + currentMediaTitle = sanitized.length > 0 ? sanitized : null; + return; + } + currentMediaTitle = null; +} + +function resolveMediaPathForJimaku(mediaPath: string | null): string | null { + return mediaPath && isRemoteMediaPath(mediaPath) && currentMediaTitle + ? currentMediaTitle + : mediaPath; +} + let subsyncInProgress = false; let initialArgs: CliArgs; let mpvSocketPath = getDefaultSocketPath(); @@ -552,6 +660,9 @@ const startupState = runStartupBootstrapRuntimeService({ updateCurrentMediaPath: (mediaPath) => { updateCurrentMediaPath(mediaPath); }, + updateCurrentMediaTitle: (mediaTitle) => { + updateCurrentMediaTitle(mediaTitle); + }, updateMpvSubtitleRenderMetrics: (patch) => { updateMpvSubtitleRenderMetrics(patch); }, @@ -944,7 +1055,9 @@ function getOverlayShortcutRuntimeHandlers() { openRuntimeOptionsPalette(); }, openJimaku: () => { - sendToVisibleOverlay("jimaku:open"); + sendToActiveOverlayWindow("jimaku:open", undefined, { + restoreOnModalClose: "jimaku", + }); }, markAudioCard: () => markLastCardAsAudioCard(), copySubtitleMultiple: (timeoutMs) => { @@ -994,6 +1107,7 @@ function cycleSecondarySubMode(): void { } function showMpvOsd(text: string): void { + appendToMpvLog(`[OSD] ${text}`); showMpvOsdRuntimeService( mpvClient, text, @@ -1003,6 +1117,19 @@ function showMpvOsd(text: string): void { ); } +function appendToMpvLog(message: string): void { + try { + fs.mkdirSync(path.dirname(DEFAULT_MPV_LOG_PATH), { recursive: true }); + fs.appendFileSync( + DEFAULT_MPV_LOG_PATH, + `[${new Date().toISOString()}] ${message}\n`, + { encoding: "utf8" }, + ); + } catch { + // best-effort logging + } +} + const numericShortcutRuntime = createNumericShortcutRuntimeService({ globalShortcut, showMpvOsd: (text) => showMpvOsd(text), @@ -1022,7 +1149,7 @@ function getSubsyncRuntimeDeps() { }, showMpvOsd: (text: string) => showMpvOsd(text), openManualPicker: (payload: SubsyncManualPayload) => { - sendToVisibleOverlay("subsync:open-manual", payload, { + sendToActiveOverlayWindow("subsync:open-manual", payload, { restoreOnModalClose: "subsync", }); }, @@ -1270,8 +1397,24 @@ function toggleOverlay(): void { toggleVisibleOverlay(); } function handleOverlayModalClosed(modal: OverlayHostedModal): void { if (!restoreVisibleOverlayOnModalClose.has(modal)) return; restoreVisibleOverlayOnModalClose.delete(modal); - if (restoreVisibleOverlayOnModalClose.size === 0) { - setVisibleOverlayVisible(false); + const layer = overlayModalAutoShownLayer.get(modal); + overlayModalAutoShownLayer.delete(modal); + if (!layer) return; + const shouldKeepLayerVisible = [...restoreVisibleOverlayOnModalClose].some( + (pendingModal) => overlayModalAutoShownLayer.get(pendingModal) === layer, + ); + if (shouldKeepLayerVisible) return; + + if (layer === "visible") { + const mainWindow = overlayManager.getMainWindow(); + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.hide(); + } + return; + } + const invisibleWindow = overlayManager.getInvisibleWindow(); + if (invisibleWindow && !invisibleWindow.isDestroyed()) { + invisibleWindow.hide(); } } @@ -1379,7 +1522,7 @@ registerAnkiJimakuIpcRuntimeService( broadcastRuntimeOptionsChanged: () => broadcastRuntimeOptionsChanged(), getFieldGroupingResolver: () => getFieldGroupingResolver(), setFieldGroupingResolver: (resolver) => setFieldGroupingResolver(resolver), - parseMediaInfo: (mediaPath) => parseMediaInfo(mediaPath), + parseMediaInfo: (mediaPath) => parseMediaInfo(resolveMediaPathForJimaku(mediaPath)), getCurrentMediaPath: () => currentMediaPath, jimakuFetchJson: (endpoint, query) => jimakuFetchJson(endpoint, query), getJimakuMaxEntryResults: () => getJimakuMaxEntryResults(), diff --git a/vendor/ani-cli b/vendor/ani-cli deleted file mode 160000 index c8aa791..0000000 --- a/vendor/ani-cli +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c8aa791982cd4c5d72234c590a2834cfa5a712e6