Skip to main content
Glama
window.ts5.84 kB
/** * Window Management for Electron */ import { BrowserWindow, screen, nativeTheme, app } from 'electron'; import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; import { IPC_CHANNELS } from './ipc/channels.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Path to store window state const getWindowStatePath = () => path.join(app.getPath('userData'), 'window-state.json'); // Colors for light and dark mode const COLORS = { light: { background: '#ffffff', }, dark: { background: '#1a1a2e', }, }; export interface WindowState { width: number; height: number; x?: number; y?: number; isMaximized: boolean; } const DEFAULT_WINDOW_STATE: WindowState = { width: 1200, height: 800, isMaximized: false, }; let mainWindow: BrowserWindow | null = null; export function createMainWindow(preloadPath: string, isDev: boolean, showOnReady: boolean = true): BrowserWindow { // Get saved window state or use defaults const windowState = getWindowState(); // Get initial theme const isDarkMode = nativeTheme.shouldUseDarkColors; const backgroundColor = isDarkMode ? COLORS.dark.background : COLORS.light.background; mainWindow = new BrowserWindow({ width: windowState.width, height: windowState.height, x: windowState.x, y: windowState.y, minWidth: 800, minHeight: 600, title: 'Mindpilot', backgroundColor, show: false, // Don't show until ready // macOS-specific: use native titlebar that respects system theme titleBarStyle: process.platform === 'darwin' ? 'default' : undefined, // Windows-specific: use dark title bar when in dark mode ...(process.platform === 'win32' && { titleBarOverlay: isDarkMode ? { color: COLORS.dark.background, symbolColor: '#ffffff', } : { color: COLORS.light.background, symbolColor: '#000000', }, }), webPreferences: { preload: preloadPath, contextIsolation: true, nodeIntegration: false, sandbox: false, // Required for preload script imports }, }); // Listen for theme changes and update window nativeTheme.on('updated', () => { if (!mainWindow) return; const newIsDarkMode = nativeTheme.shouldUseDarkColors; const newBackgroundColor = newIsDarkMode ? COLORS.dark.background : COLORS.light.background; mainWindow.setBackgroundColor(newBackgroundColor); // Update Windows titlebar overlay if (process.platform === 'win32') { mainWindow.setTitleBarOverlay({ color: newBackgroundColor, symbolColor: newIsDarkMode ? '#ffffff' : '#000000', }); } }); // Restore maximized state if (windowState.isMaximized) { mainWindow.maximize(); } // Show window when ready (unless starting hidden for MCP mode) if (showOnReady) { mainWindow.once('ready-to-show', () => { mainWindow?.show(); }); } // Save window state on close mainWindow.on('close', () => { if (mainWindow) { saveWindowState(mainWindow); } }); mainWindow.on('closed', () => { mainWindow = null; }); // Notify renderer when window is focused (for refreshing history) mainWindow.on('focus', () => { mainWindow?.webContents.send(IPC_CHANNELS.WINDOW_FOCUS); }); // Also notify when window is shown (e.g., restored from minimized) mainWindow.on('show', () => { mainWindow?.webContents.send(IPC_CHANNELS.WINDOW_FOCUS); }); // Notify renderer when window loses focus (for disabling hotkeys) mainWindow.on('blur', () => { mainWindow?.webContents.send(IPC_CHANNELS.WINDOW_BLUR); }); // Also notify when window is hidden mainWindow.on('hide', () => { mainWindow?.webContents.send(IPC_CHANNELS.WINDOW_BLUR); }); // Load the app if (isDev) { // Development: load from Vite dev server mainWindow.loadURL('http://localhost:5173'); // DevTools can be opened manually with Cmd+Option+I (macOS) or F12 (Windows/Linux) } else { // Production: load built files const indexPath = path.join(__dirname, '../public/index.html'); mainWindow.loadFile(indexPath); } return mainWindow; } export function getMainWindow(): BrowserWindow | null { return mainWindow; } function getWindowState(): WindowState { try { const statePath = getWindowStatePath(); if (fs.existsSync(statePath)) { const data = fs.readFileSync(statePath, 'utf-8'); const state = JSON.parse(data) as WindowState; // Validate the loaded state has required properties if (typeof state.width === 'number' && typeof state.height === 'number') { return state; } } } catch (error) { console.error('Failed to load window state:', error); } return DEFAULT_WINDOW_STATE; } function saveWindowState(window: BrowserWindow): void { try { const bounds = window.getBounds(); const state: WindowState = { width: bounds.width, height: bounds.height, x: bounds.x, y: bounds.y, isMaximized: window.isMaximized(), }; const statePath = getWindowStatePath(); fs.writeFileSync(statePath, JSON.stringify(state, null, 2)); } catch (error) { console.error('Failed to save window state:', error); } } export function ensureWindowVisible(): void { if (!mainWindow) return; const bounds = mainWindow.getBounds(); const display = screen.getDisplayMatching(bounds); const { workArea } = display; // Check if window is at least partially visible const isVisible = bounds.x < workArea.x + workArea.width && bounds.x + bounds.width > workArea.x && bounds.y < workArea.y + workArea.height && bounds.y + bounds.height > workArea.y; if (!isVisible) { // Center window on primary display mainWindow.center(); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/abrinsmead/mindpilot-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server