Fix overlay modal dispatch and restore behavior

This commit is contained in:
2026-02-14 00:35:30 -08:00
parent 3dfa713b29
commit cb9a599b23
3 changed files with 151 additions and 13 deletions

4
.gitmodules vendored
View File

@@ -2,7 +2,3 @@
path = vendor/texthooker-ui path = vendor/texthooker-ui
url = https://github.com/ksyasuda/texthooker-ui.git url = https://github.com/ksyasuda/texthooker-ui.git
branch = subminer branch = subminer
[submodule "vendor/ani-cli"]
path = vendor/ani-cli
url = https://github.com/pystardust/ani-cli

View File

@@ -176,6 +176,12 @@ if (process.platform === "linux") {
} }
const DEFAULT_TEXTHOOKER_PORT = 5174; const DEFAULT_TEXTHOOKER_PORT = 5174;
const DEFAULT_MPV_LOG_FILE = path.join(
os.homedir(),
".cache",
"SubMiner",
"mp.log",
);
function resolveConfigDir(): string { function resolveConfigDir(): string {
const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim(); const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim();
const baseDirs = Array.from( const baseDirs = Array.from(
@@ -212,6 +218,7 @@ function resolveConfigDir(): string {
const CONFIG_DIR = resolveConfigDir(); const CONFIG_DIR = resolveConfigDir();
const USER_DATA_PATH = CONFIG_DIR; 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 configService = new ConfigService(CONFIG_DIR);
const isDev = const isDev =
process.argv.includes("--dev") || process.argv.includes("--debug"); process.argv.includes("--dev") || process.argv.includes("--debug");
@@ -270,6 +277,7 @@ let currentSubAssText = "";
let windowTracker: BaseWindowTracker | null = null; let windowTracker: BaseWindowTracker | null = null;
let subtitlePosition: SubtitlePosition | null = null; let subtitlePosition: SubtitlePosition | null = null;
let currentMediaPath: string | null = null; let currentMediaPath: string | null = null;
let currentMediaTitle: string | null = null;
let pendingSubtitlePosition: SubtitlePosition | null = null; let pendingSubtitlePosition: SubtitlePosition | null = null;
let mecabTokenizer: MecabTokenizer | null = null; let mecabTokenizer: MecabTokenizer | null = null;
let keybindings: Keybinding[] = []; let keybindings: Keybinding[] = [];
@@ -297,8 +305,10 @@ const overlayContentMeasurementStore = createOverlayContentMeasurementStoreServi
console.warn(message); console.warn(message);
}, },
}); });
type OverlayHostedModal = "runtime-options" | "subsync"; type OverlayHostedModal = "runtime-options" | "subsync" | "jimaku";
type OverlayHostLayer = "visible" | "invisible";
const restoreVisibleOverlayOnModalClose = new Set<OverlayHostedModal>(); const restoreVisibleOverlayOnModalClose = new Set<OverlayHostedModal>();
const overlayModalAutoShownLayer = new Map<OverlayHostedModal, OverlayHostLayer>();
function getFieldGroupingResolver(): ((choice: KikuFieldGroupingChoice) => void) | null { function getFieldGroupingResolver(): ((choice: KikuFieldGroupingChoice) => void) | null {
return fieldGroupingResolver; return fieldGroupingResolver;
@@ -328,8 +338,11 @@ const fieldGroupingOverlayRuntime = createFieldGroupingOverlayRuntimeService<Ove
getResolver: () => getFieldGroupingResolver(), getResolver: () => getFieldGroupingResolver(),
setResolver: (resolver) => setFieldGroupingResolver(resolver), setResolver: (resolver) => setFieldGroupingResolver(resolver),
getRestoreVisibleOverlayOnModalClose: () => restoreVisibleOverlayOnModalClose, getRestoreVisibleOverlayOnModalClose: () => restoreVisibleOverlayOnModalClose,
sendToVisibleOverlay: (channel, payload) => {
sendToActiveOverlayWindow(channel, payload);
return true;
},
}); });
const sendToVisibleOverlay = fieldGroupingOverlayRuntime.sendToVisibleOverlay;
const createFieldGroupingCallback = const createFieldGroupingCallback =
fieldGroupingOverlayRuntime.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 { function setOverlayDebugVisualizationEnabled(enabled: boolean): void {
setOverlayDebugVisualizationEnabledRuntimeService( setOverlayDebugVisualizationEnabledRuntimeService(
overlayDebugVisualizationEnabled, 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(); } function getResolvedConfig() { return configService.getConfig(); }
@@ -437,6 +527,9 @@ function saveSubtitlePosition(position: SubtitlePosition): void {
} }
function updateCurrentMediaPath(mediaPath: unknown): void { function updateCurrentMediaPath(mediaPath: unknown): void {
if (typeof mediaPath !== "string" || !isRemoteMediaPath(mediaPath)) {
currentMediaTitle = null;
}
updateCurrentMediaPathService({ updateCurrentMediaPathService({
mediaPath, mediaPath,
currentMediaPath, 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 subsyncInProgress = false;
let initialArgs: CliArgs; let initialArgs: CliArgs;
let mpvSocketPath = getDefaultSocketPath(); let mpvSocketPath = getDefaultSocketPath();
@@ -552,6 +660,9 @@ const startupState = runStartupBootstrapRuntimeService({
updateCurrentMediaPath: (mediaPath) => { updateCurrentMediaPath: (mediaPath) => {
updateCurrentMediaPath(mediaPath); updateCurrentMediaPath(mediaPath);
}, },
updateCurrentMediaTitle: (mediaTitle) => {
updateCurrentMediaTitle(mediaTitle);
},
updateMpvSubtitleRenderMetrics: (patch) => { updateMpvSubtitleRenderMetrics: (patch) => {
updateMpvSubtitleRenderMetrics(patch); updateMpvSubtitleRenderMetrics(patch);
}, },
@@ -944,7 +1055,9 @@ function getOverlayShortcutRuntimeHandlers() {
openRuntimeOptionsPalette(); openRuntimeOptionsPalette();
}, },
openJimaku: () => { openJimaku: () => {
sendToVisibleOverlay("jimaku:open"); sendToActiveOverlayWindow("jimaku:open", undefined, {
restoreOnModalClose: "jimaku",
});
}, },
markAudioCard: () => markLastCardAsAudioCard(), markAudioCard: () => markLastCardAsAudioCard(),
copySubtitleMultiple: (timeoutMs) => { copySubtitleMultiple: (timeoutMs) => {
@@ -994,6 +1107,7 @@ function cycleSecondarySubMode(): void {
} }
function showMpvOsd(text: string): void { function showMpvOsd(text: string): void {
appendToMpvLog(`[OSD] ${text}`);
showMpvOsdRuntimeService( showMpvOsdRuntimeService(
mpvClient, mpvClient,
text, 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({ const numericShortcutRuntime = createNumericShortcutRuntimeService({
globalShortcut, globalShortcut,
showMpvOsd: (text) => showMpvOsd(text), showMpvOsd: (text) => showMpvOsd(text),
@@ -1022,7 +1149,7 @@ function getSubsyncRuntimeDeps() {
}, },
showMpvOsd: (text: string) => showMpvOsd(text), showMpvOsd: (text: string) => showMpvOsd(text),
openManualPicker: (payload: SubsyncManualPayload) => { openManualPicker: (payload: SubsyncManualPayload) => {
sendToVisibleOverlay("subsync:open-manual", payload, { sendToActiveOverlayWindow("subsync:open-manual", payload, {
restoreOnModalClose: "subsync", restoreOnModalClose: "subsync",
}); });
}, },
@@ -1270,8 +1397,24 @@ function toggleOverlay(): void { toggleVisibleOverlay(); }
function handleOverlayModalClosed(modal: OverlayHostedModal): void { function handleOverlayModalClosed(modal: OverlayHostedModal): void {
if (!restoreVisibleOverlayOnModalClose.has(modal)) return; if (!restoreVisibleOverlayOnModalClose.has(modal)) return;
restoreVisibleOverlayOnModalClose.delete(modal); restoreVisibleOverlayOnModalClose.delete(modal);
if (restoreVisibleOverlayOnModalClose.size === 0) { const layer = overlayModalAutoShownLayer.get(modal);
setVisibleOverlayVisible(false); 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(), broadcastRuntimeOptionsChanged: () => broadcastRuntimeOptionsChanged(),
getFieldGroupingResolver: () => getFieldGroupingResolver(), getFieldGroupingResolver: () => getFieldGroupingResolver(),
setFieldGroupingResolver: (resolver) => setFieldGroupingResolver(resolver), setFieldGroupingResolver: (resolver) => setFieldGroupingResolver(resolver),
parseMediaInfo: (mediaPath) => parseMediaInfo(mediaPath), parseMediaInfo: (mediaPath) => parseMediaInfo(resolveMediaPathForJimaku(mediaPath)),
getCurrentMediaPath: () => currentMediaPath, getCurrentMediaPath: () => currentMediaPath,
jimakuFetchJson: (endpoint, query) => jimakuFetchJson(endpoint, query), jimakuFetchJson: (endpoint, query) => jimakuFetchJson(endpoint, query),
getJimakuMaxEntryResults: () => getJimakuMaxEntryResults(), getJimakuMaxEntryResults: () => getJimakuMaxEntryResults(),

1
vendor/ani-cli vendored

Submodule vendor/ani-cli deleted from c8aa791982