Files
genshin-assistant/electron/appWindowManager.ts
T
2026-07-09 08:44:50 +02:00

146 lines
3.8 KiB
TypeScript

import { BrowserWindow, Menu, screen } from "electron";
import path from "node:path";
export interface AppWindowManagerOptions {
preloadPath: string;
rendererUrl?: string;
rendererFilePath: string;
onMainReadyToShow?: () => void | Promise<void>;
}
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() {
overlayWindow?.close();
return { ok: true };
}
return {
createMainWindow,
focusMainWindow,
hasMainWindow: () => Boolean(mainWindow && !mainWindow.isDestroyed()),
getMainWindow: () => mainWindow,
sendScannerCommand,
createOverlayWindow,
hideOverlayWindow,
};
}