import { join } from 'path' import { BrowserWindow, Menu, shell } from 'electron' import { is } from '@electron-toolkit/utils' import windowStateKeeper from 'electron-window-state' import icon from '../../resources/icon.png?asset' import { getAppConfig } from './config' import { quitWithoutCore, stopCore } from './core/manager' import { triggerSysProxy } from './sys/sysproxy' import { hideDockIcon, showDockIcon } from './resolve/tray' export let mainWindow: BrowserWindow | null = null let quitTimeout: NodeJS.Timeout | null = null export async function createWindow(): Promise { const { useWindowFrame = false, silentStart = false, autoQuitWithoutCore = false, autoQuitWithoutCoreDelay = 60 } = await getAppConfig() const mainWindowState = windowStateKeeper({ defaultWidth: 800, defaultHeight: 600, file: 'window-state.json' }) Menu.setApplicationMenu(null) mainWindow = new BrowserWindow({ minWidth: 800, minHeight: 600, width: mainWindowState.width, height: mainWindowState.height, x: mainWindowState.x, y: mainWindowState.y, show: false, frame: useWindowFrame, fullscreenable: false, titleBarStyle: useWindowFrame ? 'default' : 'hidden', titleBarOverlay: useWindowFrame ? false : { height: 49 }, autoHideMenuBar: true, ...(process.platform === 'linux' ? { icon: icon } : {}), webPreferences: { preload: join(__dirname, '../preload/index.cjs'), spellcheck: false, sandbox: false, devTools: true } }) mainWindowState.manage(mainWindow) setupWindowEvents(mainWindow, mainWindowState, { silentStart, autoQuitWithoutCore, autoQuitWithoutCoreDelay }) if (is.dev) { mainWindow.webContents.openDevTools() } if (is.dev && process.env['ELECTRON_RENDERER_URL']) { mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) } else { mainWindow.loadFile(join(__dirname, '../renderer/index.html')) } } interface WindowConfig { silentStart: boolean autoQuitWithoutCore: boolean autoQuitWithoutCoreDelay: number } function setupWindowEvents( window: BrowserWindow, windowState: ReturnType, config: WindowConfig ): void { const { silentStart, autoQuitWithoutCore, autoQuitWithoutCoreDelay } = config window.on('ready-to-show', () => { if (autoQuitWithoutCore && !window.isVisible()) { scheduleQuitWithoutCore(autoQuitWithoutCoreDelay) } // 开发模式下始终显示窗口 if (!silentStart || is.dev) { clearQuitTimeout() window.show() window.focusOnWebView() } }) window.webContents.on('did-fail-load', () => { window.webContents.reload() }) window.on('show', () => { showDockIcon() }) window.on('close', async (event) => { event.preventDefault() window.hide() const { autoQuitWithoutCore = false, autoQuitWithoutCoreDelay = 60, useDockIcon = true } = await getAppConfig() if (!useDockIcon) { hideDockIcon() } if (autoQuitWithoutCore) { scheduleQuitWithoutCore(autoQuitWithoutCoreDelay) } }) window.on('resized', () => { windowState.saveState(window) }) window.on('move', () => { windowState.saveState(window) }) window.on('session-end', async () => { await triggerSysProxy(false) await stopCore() }) window.webContents.setWindowOpenHandler((details) => { shell.openExternal(details.url) return { action: 'deny' } }) } function scheduleQuitWithoutCore(delaySeconds: number): void { clearQuitTimeout() quitTimeout = setTimeout(async () => { await quitWithoutCore() }, delaySeconds * 1000) } export function clearQuitTimeout(): void { if (quitTimeout) { clearTimeout(quitTimeout) quitTimeout = null } } export function triggerMainWindow(force?: boolean): void { if (!mainWindow) return getAppConfig() .then(({ triggerMainWindowBehavior = 'toggle' }) => { if (force === true || triggerMainWindowBehavior === 'toggle') { if (mainWindow?.isVisible()) { closeMainWindow() } else { showMainWindow() } } else { showMainWindow() } }) .catch(showMainWindow) } export function showMainWindow(): void { if (mainWindow) { clearQuitTimeout() mainWindow.show() mainWindow.focusOnWebView() } } export function closeMainWindow(): void { mainWindow?.close() }