small fixes

This commit is contained in:
2026-02-27 00:34:34 -08:00
parent edcd5cddb6
commit edca554db1
5 changed files with 113 additions and 20 deletions

View File

@@ -117,7 +117,7 @@ export function createOverlayWindow(
window.webContents.on('before-input-event', (event, input) => {
if (kind === 'modal') return;
if (!options.isOverlayVisible(kind)) return;
if (!window.isVisible()) return;
if (!options.tryHandleOverlayShortcutLocalFallback(input)) return;
event.preventDefault();
});

View File

@@ -225,6 +225,27 @@ test('handleOverlayModalClosed hides modal window only after all pending modals
assert.equal(window.getHideCount(), 1);
});
test('sendToActiveOverlayWindow prefers visible main overlay window for modal open', () => {
const mainWindow = createMockWindow();
mainWindow.visible = true;
const runtime = createOverlayModalRuntimeService({
getMainWindow: () => mainWindow as never,
getModalWindow: () => null,
createModalWindow: () => {
throw new Error('modal window should not be created when main overlay is visible');
},
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
});
const sent = runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
});
assert.equal(sent, true);
assert.deepEqual(mainWindow.sent, [['runtime-options:open']]);
});
test('modal runtime notifies callers when modal input state becomes active/inactive', () => {
const window = createMockWindow();
const state: boolean[] = [];
@@ -249,6 +270,8 @@ test('modal runtime notifies callers when modal input state becomes active/inact
runtime.sendToActiveOverlayWindow('subsync:open-manual', { sourceTracks: [] }, {
restoreOnModalClose: 'subsync',
});
assert.deepEqual(state, []);
runtime.notifyOverlayModalOpened('runtime-options');
assert.deepEqual(state, [true]);
runtime.handleOverlayModalClosed('runtime-options');
@@ -258,6 +281,32 @@ test('modal runtime notifies callers when modal input state becomes active/inact
assert.deepEqual(state, [true, false]);
});
test('handleOverlayModalClosed resets modal state even when modal window does not exist', () => {
const state: boolean[] = [];
const runtime = createOverlayModalRuntimeService(
{
getMainWindow: () => null,
getModalWindow: () => null,
createModalWindow: () => null,
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
},
{
onModalStateChange: (active: boolean): void => {
state.push(active);
},
},
);
runtime.sendToActiveOverlayWindow('runtime-options:open', undefined, {
restoreOnModalClose: 'runtime-options',
});
runtime.notifyOverlayModalOpened('runtime-options');
runtime.handleOverlayModalClosed('runtime-options');
assert.deepEqual(state, [true, false]);
});
test('handleOverlayModalClosed hides modal window for single kiku modal', () => {
const window = createMockWindow();
const runtime = createOverlayModalRuntimeService({

View File

@@ -73,6 +73,12 @@ const isWindowReadyForIpc = (window: BrowserWindow): boolean => {
return currentURL !== '' && currentURL !== 'about:blank';
};
const elevateModalWindow = (window: BrowserWindow): void => {
if (window.isDestroyed()) return;
window.setAlwaysOnTop(true, 'screen-saver', 1);
window.moveTop();
};
const sendOrQueueForWindow = (
window: BrowserWindow,
sendNow: (window: BrowserWindow) => void,
@@ -95,7 +101,10 @@ const isWindowReadyForIpc = (window: BrowserWindow): boolean => {
passThroughMouseEvents: boolean;
} = { passThroughMouseEvents: false },
): void => {
if (!window.isVisible()) {
window.show();
}
elevateModalWindow(window);
if (options.passThroughMouseEvents) {
window.setIgnoreMouseEvents(true, { forward: true });
} else {
@@ -107,6 +116,22 @@ const isWindowReadyForIpc = (window: BrowserWindow): boolean => {
}
};
const ensureModalWindowInteractive = (window: BrowserWindow): void => {
if (window.isVisible()) {
window.setIgnoreMouseEvents(false);
if (!window.isFocused()) {
window.focus();
}
if (!window.webContents.isFocused()) {
window.webContents.focus();
}
elevateModalWindow(window);
return;
}
showModalWindow(window);
};
const showOverlayWindowForModal = (window: BrowserWindow): void => {
window.show();
if (!window.isFocused()) {
@@ -137,7 +162,7 @@ const isWindowReadyForIpc = (window: BrowserWindow): boolean => {
if (!targetWindow || targetWindow.isDestroyed() || targetWindow.isVisible()) {
return;
}
showModalWindow(targetWindow, { passThroughMouseEvents: true });
showModalWindow(targetWindow, { passThroughMouseEvents: false });
}, MODAL_REVEAL_FALLBACK_DELAY_MS);
};
@@ -149,6 +174,7 @@ const isWindowReadyForIpc = (window: BrowserWindow): boolean => {
const restoreOnModalClose = runtimeOptions?.restoreOnModalClose;
const sendNow = (window: BrowserWindow): void => {
ensureModalWindowInteractive(window);
if (payload === undefined) {
window.webContents.send(channel);
} else {
@@ -157,17 +183,24 @@ const isWindowReadyForIpc = (window: BrowserWindow): boolean => {
};
if (restoreOnModalClose) {
restoreVisibleOverlayOnModalClose.add(restoreOnModalClose);
const mainWindow = getTargetOverlayWindow();
if (mainWindow && !mainWindow.isDestroyed() && mainWindow.isVisible()) {
sendOrQueueForWindow(mainWindow, (window) => {
if (payload === undefined) {
window.webContents.send(channel);
} else {
window.webContents.send(channel, payload);
}
});
return true;
}
const modalWindow = resolveModalWindow();
if (!modalWindow) return false;
deps.setModalWindowBounds(deps.getModalGeometry());
const wasVisible = modalWindow.isVisible();
const wasModalActive = restoreVisibleOverlayOnModalClose.size > 0;
restoreVisibleOverlayOnModalClose.add(restoreOnModalClose);
if (!wasModalActive) {
notifyModalStateChange(true);
}
if (!wasVisible) {
scheduleModalWindowReveal(modalWindow);
} else if (!modalWindow.isFocused()) {
@@ -199,17 +232,21 @@ const isWindowReadyForIpc = (window: BrowserWindow): boolean => {
const handleOverlayModalClosed = (modal: OverlayHostedModal): void => {
if (!restoreVisibleOverlayOnModalClose.has(modal)) return;
restoreVisibleOverlayOnModalClose.delete(modal);
const modalWindow = deps.getModalWindow();
if (!modalWindow || modalWindow.isDestroyed()) return;
if (restoreVisibleOverlayOnModalClose.size === 0) {
clearPendingModalWindowReveal();
notifyModalStateChange(false);
}
const modalWindow = deps.getModalWindow();
if (!modalWindow || modalWindow.isDestroyed()) return;
if (restoreVisibleOverlayOnModalClose.size === 0) {
modalWindow.hide();
}
};
const notifyOverlayModalOpened = (modal: OverlayHostedModal): void => {
if (!restoreVisibleOverlayOnModalClose.has(modal)) return;
notifyModalStateChange(true);
const targetWindow = deps.getModalWindow();
clearPendingModalWindowReveal();
if (!targetWindow || targetWindow.isDestroyed()) {
@@ -218,6 +255,7 @@ const isWindowReadyForIpc = (window: BrowserWindow): boolean => {
if (targetWindow.isVisible()) {
targetWindow.setIgnoreMouseEvents(false);
elevateModalWindow(targetWindow);
if (!targetWindow.isFocused()) {
targetWindow.focus();
}

View File

@@ -41,5 +41,5 @@ test('overlay runtime bootstrap runs core init and applies post-init state', ()
initialize();
assert.equal(initialized, true);
assert.deepEqual(calls, ['options', 'core', 'initialized:yes', 'warmups']);
assert.deepEqual(calls, ['options', 'initialized:yes', 'core', 'warmups']);
});

View File

@@ -41,8 +41,14 @@ export function createInitializeOverlayRuntimeHandler(deps: {
}) {
return (): void => {
if (deps.isOverlayRuntimeInitialized()) return;
deps.initializeOverlayRuntimeCore(deps.buildOptions());
const options = deps.buildOptions();
deps.setOverlayRuntimeInitialized(true);
try {
deps.initializeOverlayRuntimeCore(options);
} catch (error) {
deps.setOverlayRuntimeInitialized(false);
throw error;
}
deps.startBackgroundWarmups();
};
}