From 134af5775c626cd68812c6841ca2282960e9229c Mon Sep 17 00:00:00 2001 From: luciano Date: Wed, 29 Jul 2026 23:40:01 +0200 Subject: [PATCH] Output window can no longer be closed by accident Cmd+W / the red button on the output window are ignored while the control panel is open: the audio engine and all visual state live in that window, so an accidental close killed the whole show with no way to reopen it. If the window dies anyway (renderer crash) it is recreated automatically. Quitting normally (closing the control panel or Cmd+Q) still works. Co-Authored-By: Claude Fable 5 --- main.js | 70 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/main.js b/main.js index 212eca5..e1f054f 100644 --- a/main.js +++ b/main.js @@ -11,6 +11,51 @@ function externalDisplay() { return screen.getAllDisplays().find(d => d.id !== primary.id) || primary; } +let quitting = false; + +function createOutputWindow() { + const primary = screen.getPrimaryDisplay(); + const ext = externalDisplay(); + const hasExternal = ext.id !== primary.id; + + outputWin = new BrowserWindow({ + width: hasExternal ? ext.bounds.width : 960, + height: hasExternal ? ext.bounds.height : 600, + x: hasExternal ? ext.bounds.x : primary.bounds.x + 520, + y: hasExternal ? ext.bounds.y : primary.bounds.y + 60, + title: 'DJ Visualizer — Output', + backgroundColor: '#000000', + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + contextIsolation: true, + nodeIntegration: false, + additionalArguments: ['--role=output'], + // Let audio loaded via IPC play without a click inside the output window. + autoplayPolicy: 'no-user-gesture-required' + } + }); + outputWin.loadFile(path.join(__dirname, 'src', 'output.html')); + if (hasExternal) { + outputWin.once('ready-to-show', () => placeOutputOnDisplay(ext, true)); + } + + // The output window hosts the audio engine and all visual state: closing it + // by accident (Cmd+W, red button) would kill the show. Ignore close requests + // while the control panel is open; only app quit may close it. + outputWin.on('close', (e) => { + if (!quitting && controlWin && !controlWin.isDestroyed()) e.preventDefault(); + }); + // Safety net: if it dies anyway (e.g. a renderer crash), bring it back. + outputWin.on('closed', () => { + outputWin = null; + if (!quitting && controlWin && !controlWin.isDestroyed()) createOutputWindow(); + }); + outputWin.webContents.on('render-process-gone', () => { + if (quitting) return; + if (outputWin && !outputWin.isDestroyed()) outputWin.destroy(); + }); +} + function createWindows() { const primary = screen.getPrimaryDisplay(); const ext = externalDisplay(); @@ -38,33 +83,14 @@ function createWindows() { controlWin.loadFile(path.join(__dirname, 'src', 'control.html')); // --- Output window (the visuals) --- - outputWin = new BrowserWindow({ - width: hasExternal ? ext.bounds.width : 960, - height: hasExternal ? ext.bounds.height : 600, - x: hasExternal ? ext.bounds.x : primary.bounds.x + 520, - y: hasExternal ? ext.bounds.y : primary.bounds.y + 60, - title: 'DJ Visualizer — Output', - backgroundColor: '#000000', - webPreferences: { - preload: path.join(__dirname, 'preload.js'), - contextIsolation: true, - nodeIntegration: false, - additionalArguments: ['--role=output'], - // Let audio loaded via IPC play without a click inside the output window. - autoplayPolicy: 'no-user-gesture-required' - } - }); - outputWin.loadFile(path.join(__dirname, 'src', 'output.html')); - if (hasExternal) { - outputWin.once('ready-to-show', () => placeOutputOnDisplay(ext, true)); - } + createOutputWindow(); controlWin.on('closed', () => { + quitting = true; // closing the panel ends the show: let the output close controlWin = null; if (outputWin) outputWin.close(); app.quit(); }); - outputWin.on('closed', () => { outputWin = null; }); } async function ensureMicAccess() { @@ -86,6 +112,8 @@ ipcMain.handle('cam:ensure', async () => { } catch (e) { return true; } }); +app.on('before-quit', () => { quitting = true; }); + app.whenReady().then(async () => { if (process.platform === 'darwin' && app.dock) { try { app.dock.setIcon(path.join(__dirname, 'build', 'icon_1024.png')); } catch (e) { /* non-fatal */ }