import { app, BrowserWindow, desktopCapturer, session, shell } from 'electron'; import * as fs from 'fs'; import * as path from 'path'; let mainWindow: BrowserWindow | null = null; const WINDOW_STATE_CHANGED_CHANNEL = 'window-state-changed'; function getAssetPath(...segments: string[]): string { const basePath = app.isPackaged ? path.join(process.resourcesPath, 'images') : path.join(__dirname, '..', '..', '..', 'images'); return path.join(basePath, ...segments); } function getExistingAssetPath(...segments: string[]): string | undefined { const assetPath = getAssetPath(...segments); return fs.existsSync(assetPath) ? assetPath : undefined; } function getWindowIconPath(): string | undefined { if (process.platform === 'win32') return getExistingAssetPath('windows', 'icon.ico'); if (process.platform === 'linux') return getExistingAssetPath('icon.png'); return undefined; } export function getDockIconPath(): string | undefined { return getExistingAssetPath('macos', '1024x1024.png'); } export { getWindowIconPath }; export function getMainWindow(): BrowserWindow | null { return mainWindow; } function emitWindowState(): void { if (!mainWindow || mainWindow.isDestroyed()) { return; } mainWindow.webContents.send(WINDOW_STATE_CHANGED_CHANNEL, { isFocused: mainWindow.isFocused(), isMinimized: mainWindow.isMinimized() }); } export async function createWindow(): Promise { const windowIconPath = getWindowIconPath(); mainWindow = new BrowserWindow({ width: 1400, height: 900, minWidth: 800, minHeight: 600, frame: false, titleBarStyle: 'hidden', backgroundColor: '#0a0a0f', ...(windowIconPath ? { icon: windowIconPath } : {}), webPreferences: { backgroundThrottling: false, nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, '..', 'preload.js'), webSecurity: true } }); if (process.platform === 'linux') { session.defaultSession.setDisplayMediaRequestHandler( async (_request, respond) => { // On Linux/Wayland the system picker (useSystemPicker: true) handles // the portal. This handler is only reached if the system picker is // unavailable (e.g. X11 without a portal). Fall back to // desktopCapturer so the user still gets something. try { const sources = await desktopCapturer.getSources({ types: ['window', 'screen'], thumbnailSize: { width: 150, height: 150 } }); const firstSource = sources[0]; if (firstSource) { respond({ video: firstSource }); return; } } catch { // desktopCapturer also unavailable } respond({}); }, { useSystemPicker: true } ); } if (process.env['NODE_ENV'] === 'development') { const devUrl = process.env['SSL'] === 'true' ? 'https://localhost:4200' : 'http://localhost:4200'; await mainWindow.loadURL(devUrl); if (process.env['DEBUG_DEVTOOLS'] === '1') { mainWindow.webContents.openDevTools(); } } else { await mainWindow.loadFile(path.join(__dirname, '..', '..', 'client', 'browser', 'index.html')); } mainWindow.on('closed', () => { mainWindow = null; }); mainWindow.on('focus', () => { mainWindow?.flashFrame(false); emitWindowState(); }); mainWindow.on('blur', () => { emitWindowState(); }); mainWindow.on('minimize', () => { emitWindowState(); }); mainWindow.on('restore', () => { emitWindowState(); }); emitWindowState(); mainWindow.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: 'deny' }; }); mainWindow.webContents.on('will-navigate', (event, url) => { const currentUrl = mainWindow?.webContents.getURL(); const isSameOrigin = new URL(url).origin === new URL(currentUrl || '').origin; if (!isSameOrigin) { event.preventDefault(); shell.openExternal(url); } }); }