From 020d8ba82d9936513939dc9874869c644a11bfb5 Mon Sep 17 00:00:00 2001 From: Amrit Subramanian Date: Fri, 3 Oct 2025 16:18:18 -0400 Subject: [PATCH] configurable themes --- index.html | 52 ++++++++++ main.ts | 32 +++++- renderer.ts | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++-- styles.css | 10 +- 4 files changed, 374 insertions(+), 10 deletions(-) diff --git a/index.html b/index.html index 8a7cb8e..7a83fbd 100644 --- a/index.html +++ b/index.html @@ -19,6 +19,7 @@ + + + diff --git a/main.ts b/main.ts index aff9140..30bbe84 100644 --- a/main.ts +++ b/main.ts @@ -212,7 +212,10 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => { let dataBuffer = ""; ptyProcess.onData((data) => { - mainWindow.webContents.send("session-output", sessionId, data); + // Only send data if window still exists and is not destroyed + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("session-output", sessionId, data); + } // Detect when terminal is ready if (!terminalReady) { @@ -309,7 +312,10 @@ ipcMain.on("reopen-session", (event, sessionId: string) => { let dataBuffer = ""; ptyProcess.onData((data) => { - mainWindow.webContents.send("session-output", sessionId, data); + // Only send data if window still exists and is not destroyed + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("session-output", sessionId, data); + } // Detect when terminal is ready and auto-run agent if (!terminalReady) { @@ -389,6 +395,15 @@ ipcMain.on("rename-session", (_event, sessionId: string, newName: string) => { } }); +// Terminal settings handlers +ipcMain.handle("get-terminal-settings", () => { + return (store as any).get("terminalSettings"); +}); + +ipcMain.handle("save-terminal-settings", (_event, settings: any) => { + (store as any).set("terminalSettings", settings); +}); + // MCP Server management functions async function listMcpServers() { try { @@ -529,6 +544,19 @@ const createWindow = () => { const sessions = getPersistedSessions(); mainWindow.webContents.send("load-persisted-sessions", sessions); }); + + // Clean up PTY processes when window is closed + mainWindow.on("closed", () => { + // Kill all active PTY processes + activePtyProcesses.forEach((ptyProcess, sessionId) => { + try { + ptyProcess.kill(); + } catch (error) { + console.error(`Error killing PTY for session ${sessionId}:`, error); + } + }); + activePtyProcesses.clear(); + }); }; app.whenReady().then(() => { diff --git a/renderer.ts b/renderer.ts index a391659..701094f 100644 --- a/renderer.ts +++ b/renderer.ts @@ -40,19 +40,199 @@ interface McpServer { type?: "stdio" | "sse"; } +interface TerminalSettings { + fontFamily: string; + fontSize: number; + theme: string; // Theme preset name + cursorBlink: boolean; +} + +interface ThemeColors { + background: string; + foreground: string; + cursor?: string; + cursorAccent?: string; + selection?: string; + black?: string; + red?: string; + green?: string; + yellow?: string; + blue?: string; + magenta?: string; + cyan?: string; + white?: string; + brightBlack?: string; + brightRed?: string; + brightGreen?: string; + brightYellow?: string; + brightBlue?: string; + brightMagenta?: string; + brightCyan?: string; + brightWhite?: string; +} + +// Theme presets +const THEME_PRESETS: Record = { + "macos-light": { + background: "#ffffff", + foreground: "#000000", + cursor: "#000000", + selection: "#b4d5fe", + black: "#000000", + red: "#c23621", + green: "#25bc24", + yellow: "#adad27", + blue: "#492ee1", + magenta: "#d338d3", + cyan: "#33bbc8", + white: "#cbcccd", + brightBlack: "#818383", + brightRed: "#fc391f", + brightGreen: "#31e722", + brightYellow: "#eaec23", + brightBlue: "#5833ff", + brightMagenta: "#f935f8", + brightCyan: "#14f0f0", + brightWhite: "#e9ebeb", + }, + "macos-dark": { + background: "#000000", + foreground: "#ffffff", + cursor: "#ffffff", + selection: "#4d4d4d", + black: "#000000", + red: "#c23621", + green: "#25bc24", + yellow: "#adad27", + blue: "#492ee1", + magenta: "#d338d3", + cyan: "#33bbc8", + white: "#cbcccd", + brightBlack: "#818383", + brightRed: "#fc391f", + brightGreen: "#31e722", + brightYellow: "#eaec23", + brightBlue: "#5833ff", + brightMagenta: "#f935f8", + brightCyan: "#14f0f0", + brightWhite: "#e9ebeb", + }, + "solarized-dark": { + background: "#002b36", + foreground: "#839496", + cursor: "#839496", + selection: "#073642", + black: "#073642", + red: "#dc322f", + green: "#859900", + yellow: "#b58900", + blue: "#268bd2", + magenta: "#d33682", + cyan: "#2aa198", + white: "#eee8d5", + brightBlack: "#002b36", + brightRed: "#cb4b16", + brightGreen: "#586e75", + brightYellow: "#657b83", + brightBlue: "#839496", + brightMagenta: "#6c71c4", + brightCyan: "#93a1a1", + brightWhite: "#fdf6e3", + }, + "dracula": { + background: "#282a36", + foreground: "#f8f8f2", + cursor: "#f8f8f2", + selection: "#44475a", + black: "#21222c", + red: "#ff5555", + green: "#50fa7b", + yellow: "#f1fa8c", + blue: "#bd93f9", + magenta: "#ff79c6", + cyan: "#8be9fd", + white: "#f8f8f2", + brightBlack: "#6272a4", + brightRed: "#ff6e6e", + brightGreen: "#69ff94", + brightYellow: "#ffffa5", + brightBlue: "#d6acff", + brightMagenta: "#ff92df", + brightCyan: "#a4ffff", + brightWhite: "#ffffff", + }, + "one-dark": { + background: "#282c34", + foreground: "#abb2bf", + cursor: "#528bff", + selection: "#3e4451", + black: "#282c34", + red: "#e06c75", + green: "#98c379", + yellow: "#e5c07b", + blue: "#61afef", + magenta: "#c678dd", + cyan: "#56b6c2", + white: "#abb2bf", + brightBlack: "#5c6370", + brightRed: "#e06c75", + brightGreen: "#98c379", + brightYellow: "#e5c07b", + brightBlue: "#61afef", + brightMagenta: "#c678dd", + brightCyan: "#56b6c2", + brightWhite: "#ffffff", + }, + "github-dark": { + background: "#0d1117", + foreground: "#c9d1d9", + cursor: "#58a6ff", + selection: "#163c61", + black: "#484f58", + red: "#ff7b72", + green: "#3fb950", + yellow: "#d29922", + blue: "#58a6ff", + magenta: "#bc8cff", + cyan: "#39c5cf", + white: "#b1bac4", + brightBlack: "#6e7681", + brightRed: "#ffa198", + brightGreen: "#56d364", + brightYellow: "#e3b341", + brightBlue: "#79c0ff", + brightMagenta: "#d2a8ff", + brightCyan: "#56d4dd", + brightWhite: "#f0f6fc", + }, +}; + +// Detect system theme +function getSystemTheme(): "light" | "dark" { + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; +} + +// Default settings - macOS Terminal matching system theme +const DEFAULT_SETTINGS: TerminalSettings = { + fontFamily: "Menlo, Monaco, 'Courier New', monospace", + fontSize: 11, + theme: getSystemTheme() === "dark" ? "macos-dark" : "macos-light", + cursorBlink: false, +}; + const sessions = new Map(); let activeSessionId: string | null = null; let mcpServers: McpServer[] = []; +let terminalSettings: TerminalSettings = { ...DEFAULT_SETTINGS }; function createTerminalUI(sessionId: string) { + const themeColors = THEME_PRESETS[terminalSettings.theme] || THEME_PRESETS["macos-dark"]; + const term = new Terminal({ - cursorBlink: true, - fontSize: 14, - fontFamily: 'Menlo, Monaco, "Courier New", monospace', - theme: { - background: "#000000", - foreground: "#ffffff", - }, + cursorBlink: terminalSettings.cursorBlink, + fontSize: terminalSettings.fontSize, + fontFamily: terminalSettings.fontFamily, + theme: themeColors, }); const fitAddon = new FitAddon(); @@ -986,3 +1166,99 @@ ipcRenderer.on("mcp-servers-updated", (_event, servers: McpServer[]) => { // Load MCP servers on startup loadMcpServers(); + +// Settings Modal handling +const settingsModal = document.getElementById("settings-modal"); +const openSettingsBtn = document.getElementById("open-settings"); +const cancelSettingsBtn = document.getElementById("cancel-settings"); +const resetSettingsBtn = document.getElementById("reset-settings"); +const saveSettingsBtn = document.getElementById("save-settings"); + +const settingsTheme = document.getElementById("settings-theme") as HTMLSelectElement; +const settingsFontFamily = document.getElementById("settings-font-family") as HTMLSelectElement; +const settingsFontSize = document.getElementById("settings-font-size") as HTMLInputElement; +const settingsCursorBlink = document.getElementById("settings-cursor-blink") as HTMLInputElement; + +// Load saved settings on startup +async function loadSettings() { + const savedSettings = await ipcRenderer.invoke("get-terminal-settings"); + if (savedSettings) { + terminalSettings = { ...DEFAULT_SETTINGS, ...savedSettings }; + } +} + +// Populate settings form +function populateSettingsForm() { + // Set theme + settingsTheme.value = terminalSettings.theme; + + // Set font family - match against dropdown options + const fontOptions = Array.from(settingsFontFamily.options); + const matchingOption = fontOptions.find(opt => opt.value === terminalSettings.fontFamily); + if (matchingOption) { + settingsFontFamily.value = matchingOption.value; + } else { + // Default to first option (Menlo) if no match + settingsFontFamily.selectedIndex = 0; + } + + settingsFontSize.value = terminalSettings.fontSize.toString(); + settingsCursorBlink.checked = terminalSettings.cursorBlink; +} + +// Apply settings to all existing terminals +function applySettingsToAllTerminals() { + const themeColors = THEME_PRESETS[terminalSettings.theme] || THEME_PRESETS["macos-dark"]; + + sessions.forEach((session) => { + if (session.terminal) { + session.terminal.options.fontFamily = terminalSettings.fontFamily; + session.terminal.options.fontSize = terminalSettings.fontSize; + session.terminal.options.cursorBlink = terminalSettings.cursorBlink; + session.terminal.options.theme = themeColors; + + // Refresh terminal to apply changes + if (session.fitAddon) { + session.fitAddon.fit(); + } + } + }); +} + +// Open settings modal +openSettingsBtn?.addEventListener("click", () => { + populateSettingsForm(); + settingsModal?.classList.remove("hidden"); +}); + +// Cancel settings +cancelSettingsBtn?.addEventListener("click", () => { + settingsModal?.classList.add("hidden"); +}); + +// Reset settings to default +resetSettingsBtn?.addEventListener("click", () => { + terminalSettings = { ...DEFAULT_SETTINGS }; + populateSettingsForm(); +}); + +// Save settings +saveSettingsBtn?.addEventListener("click", async () => { + // Read values from form + terminalSettings.theme = settingsTheme.value; + terminalSettings.fontFamily = settingsFontFamily.value || DEFAULT_SETTINGS.fontFamily; + terminalSettings.fontSize = parseInt(settingsFontSize.value) || DEFAULT_SETTINGS.fontSize; + terminalSettings.cursorBlink = settingsCursorBlink.checked; + + // Save to electron-store + await ipcRenderer.invoke("save-terminal-settings", terminalSettings); + + // Apply to all existing terminals + applySettingsToAllTerminals(); + + // Close modal + settingsModal?.classList.add("hidden"); +}); + +// Load settings on startup +loadSettings(); diff --git a/styles.css b/styles.css index 373eb65..55ec8ae 100644 --- a/styles.css +++ b/styles.css @@ -9,13 +9,17 @@ } .sidebar-header { - @apply p-4 border-b border-gray-700; + @apply p-4 border-b border-gray-700 flex items-center justify-between; } .sidebar-title { @apply text-white font-semibold text-lg; } + .settings-btn { + @apply text-gray-400 hover:text-white hover:bg-gray-700 text-lg w-8 h-8 flex items-center justify-center rounded transition-all; + } + .sidebar-content { @apply flex-1 overflow-y-auto; } @@ -177,6 +181,10 @@ @apply w-4 h-4 rounded border-gray-600 bg-gray-700 text-blue-600 focus:ring-blue-500 focus:ring-2 cursor-pointer; } + .color-input { + @apply bg-gray-700 border border-gray-600 rounded px-2 py-1 h-10 w-20 cursor-pointer; + } + .btn-secondary { @apply bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded text-sm font-medium transition; }