diff --git a/main.js b/main.js index 16ca28a..79a6d7a 100644 --- a/main.js +++ b/main.js @@ -5,7 +5,7 @@ const { createAudioBridge } = require("./audioBridge"); const { createDefaultSettings, createSettingsStore, createThemeDefaults, sanitizeSettings } = require("./settingsStore"); const ThemeAgent = require('./themeAgent'); -let overlayWindow; +const overlayWindows = new Map(); let lastBridgeMode = null; let lastBridgeReason = null; let audioBridge; @@ -226,11 +226,19 @@ const THEME_LABELS = { auroraDrift: "Aurora Drift" }; -function createOverlayWindow() { - const primaryDisplay = screen.getPrimaryDisplay(); - const { bounds } = primaryDisplay; +function createOverlayForDisplay(display) { + const { bounds, id } = display; - overlayWindow = new BrowserWindow({ + const isEnabled = visualizerSettings.monitors && visualizerSettings.monitors[id] ? visualizerSettings.monitors[id].enabled !== false : true; + if (!isEnabled) { + return; + } + + if (overlayWindows.has(id)) { + return; + } + + const win = new BrowserWindow({ x: bounds.x, y: bounds.y, width: bounds.width, @@ -276,6 +284,27 @@ function createOverlayWindow() { stopSimulatedAudioFallback(); overlayWindow = null; }); + + overlayWindows.set(id, win); +} + +function syncOverlayWindows() { + const displays = screen.getAllDisplays(); + const currentDisplayIds = new Set(displays.map(d => d.id)); + + for (const [id, win] of overlayWindows.entries()) { + const isEnabled = visualizerSettings.monitors && visualizerSettings.monitors[id] ? visualizerSettings.monitors[id].enabled !== false : true; + if (!currentDisplayIds.has(id) || !isEnabled) { + if (!win.isDestroyed()) { + win.close(); + } + overlayWindows.delete(id); + } + } + + for (const display of displays) { + createOverlayForDisplay(display); + } } function sendAudioLevel(value, source) { @@ -283,14 +312,11 @@ function sendAudioLevel(value, source) { return; } - if (!overlayWindow || overlayWindow.isDestroyed()) { - return; + for (const win of overlayWindows.values()) { + if (!win.isDestroyed()) { + win.webContents.send("audio-level", { value, source }); + } } - - overlayWindow.webContents.send("audio-level", { - value, - source - }); } function getRendererSettings() { @@ -371,8 +397,10 @@ function toggleHidden() { } function reloadVisualizer() { - if (!overlayWindow || overlayWindow.isDestroyed()) { - return; + for (const win of overlayWindows.values()) { + if (!win.isDestroyed()) { + win.webContents.reloadIgnoringCache(); + } } stopSimulatedAudioFallback(); @@ -527,12 +555,6 @@ function resizeOverlayToPrimaryDisplay() { if (!overlayWindow || overlayWindow.isDestroyed()) { return; } - - const { bounds } = screen.getPrimaryDisplay(); - overlayWindow.setBounds(bounds); - overlayWindow.setAlwaysOnTop(true, "screen-saver"); - overlayWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); - overlayWindow.moveTop(); } function handleAudioBridgeStatusChange(status) { @@ -1528,6 +1550,26 @@ function refreshTrayMenu() { label: "Reload Visualizer", click: () => reloadVisualizer() }, + { type: "separator" }, + { + label: "Displays", + submenu: screen.getAllDisplays().map((display, index) => { + const isEnabled = visualizerSettings.monitors && visualizerSettings.monitors[display.id] ? visualizerSettings.monitors[display.id].enabled !== false : true; + return { + label: `Display ${index + 1} (${display.bounds.width}x${display.bounds.height})`, + type: "checkbox", + checked: isEnabled, + click: () => { + updateSettings({ + monitors: { + ...visualizerSettings.monitors, + [display.id]: { enabled: !isEnabled } + } + }); + } + }; + }) + }, { label: "Launch on Startup", type: "checkbox", @@ -1998,20 +2040,23 @@ app.whenReady().then(() => { refreshTrayMenu(); - screen.on("display-metrics-changed", resizeOverlayToPrimaryDisplay); - screen.on("display-added", resizeOverlayToPrimaryDisplay); - screen.on("display-removed", resizeOverlayToPrimaryDisplay); + screen.on("display-metrics-changed", () => { + syncOverlayWindows(); + resizeAllOverlays(); + }); + screen.on("display-added", syncOverlayWindows); + screen.on("display-removed", syncOverlayWindows); app.on("activate", () => { - if (BrowserWindow.getAllWindows().length === 0) { - createOverlayWindow(); + if (overlayWindows.size === 0) { + syncOverlayWindows(); } }); }); app.on("second-instance", () => { - if (overlayWindow && !overlayWindow.isDestroyed()) { - resizeOverlayToPrimaryDisplay(); + if (overlayWindows.size > 0) { + resizeAllOverlays(); sendVisualizerSettings(); } }); diff --git a/renderer.js b/renderer.js index 17dda34..379f066 100644 --- a/renderer.js +++ b/renderer.js @@ -593,7 +593,9 @@ function updateAudioLevel(now) { const helperDriven = latestSource === "helper"; const breathing = helperDriven ? 0.003 : 0.028 * (Math.sin(now * 0.00023) + 1); const response = helperDriven ? 0.2 : 0.018; - smoothedLevel += ((incomingLevel + breathing) - smoothedLevel) * response; + const deltaTime = lastFrameAt ? now - lastFrameAt : 16.6; + const timeScale = deltaTime / 16.6; + smoothedLevel += ((incomingLevel + breathing) - smoothedLevel) * Math.min(1, response * timeScale); } function renderFrame(now) {