import { BrowserWindow, Menu, screen } from "electron"; import path from "node:path"; export interface AppWindowManagerOptions { preloadPath: string; rendererUrl?: string; rendererFilePath: string; onMainReadyToShow?: () => void | Promise; } export interface AppWindowManager { createMainWindow: () => void; focusMainWindow: () => { ok: boolean }; hasMainWindow: () => boolean; getMainWindow: () => BrowserWindow | null; sendScannerCommand: (command: unknown) => void; createOverlayWindow: () => void; hideOverlayWindow: () => { ok: boolean }; } export function createAppWindowManager({ preloadPath, rendererUrl, rendererFilePath, onMainReadyToShow, }: AppWindowManagerOptions): AppWindowManager { let mainWindow: BrowserWindow | null = null; let overlayWindow: BrowserWindow | null = null; function createMainWindow() { Menu.setApplicationMenu(null); mainWindow = new BrowserWindow({ width: 1320, height: 860, minWidth: 1120, minHeight: 720, backgroundColor: "#090711", title: "Genshin Artifact Assistant", show: false, autoHideMenuBar: true, webPreferences: { preload: preloadPath, contextIsolation: true, nodeIntegration: false, }, }); mainWindow.setMenuBarVisibility(false); mainWindow.on("closed", () => { mainWindow = null; }); mainWindow.once("ready-to-show", () => { void onMainReadyToShow?.(); focusMainWindow(); }); mainWindow.webContents.once("did-finish-load", () => { setTimeout(() => focusMainWindow(), 350); }); if (rendererUrl) { mainWindow.loadURL(rendererUrl); } else { mainWindow.loadFile(rendererFilePath); } } function focusMainWindow() { if (!mainWindow || mainWindow.isDestroyed()) return { ok: false }; if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.show(); // Genshin often keeps foreground focus after a scan click. Toggling // always-on-top for one tick nudges Windows to surface the dashboard again // without leaving it pinned above other apps. mainWindow.setAlwaysOnTop(true, "screen-saver"); mainWindow.focus(); setTimeout(() => { if (!mainWindow || mainWindow.isDestroyed()) return; mainWindow.setAlwaysOnTop(false); mainWindow.focus(); }, 250); return { ok: true }; } function sendScannerCommand(command: unknown) { if (!mainWindow || mainWindow.isDestroyed()) return; mainWindow.webContents.send("scanner:command", command); } function createOverlayWindow() { if (overlayWindow) { overlayWindow.show(); return; } const display = screen.getPrimaryDisplay(); overlayWindow = new BrowserWindow({ x: display.workArea.x, y: display.workArea.y, width: display.workArea.width, height: display.workArea.height, transparent: true, frame: false, alwaysOnTop: true, skipTaskbar: true, resizable: false, focusable: false, webPreferences: { preload: preloadPath, contextIsolation: true, nodeIntegration: false, }, }); overlayWindow.setIgnoreMouseEvents(true, { forward: true }); if (rendererUrl) { overlayWindow.loadURL(`${rendererUrl}?overlay=1`); } else { overlayWindow.loadFile(rendererFilePath, { query: { overlay: "1" }, }); } overlayWindow.on("closed", () => { overlayWindow = null; }); } function hideOverlayWindow() { if (!overlayWindow || overlayWindow.isDestroyed()) return { ok: false }; overlayWindow.close(); return { ok: true }; } return { createMainWindow, focusMainWindow, hasMainWindow: () => Boolean(mainWindow && !mainWindow.isDestroyed()), getMainWindow: () => mainWindow, sendScannerCommand, createOverlayWindow, hideOverlayWindow, }; }