Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/main/ipc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
const fs = require('fs');
const path = require('path');
const {
ipcMain, dialog, shell, BrowserWindow,
ipcMain, dialog, shell, BrowserWindow, app,
} = require('electron');
const os = require('os');
const { spawn } = require('child_process');
Expand Down Expand Up @@ -492,6 +492,22 @@ function registerIpc({ openCscopeWindow, getInitialFolder, getStartupLogs }) {
ipcMain.handle('app:getCliFolder', () => (typeof getInitialFolder === 'function' ? (getInitialFolder() || null) : null));
ipcMain.handle('app:getStartupLogs', () => (typeof getStartupLogs === 'function' ? getStartupLogs() : []));

// Renderer caches the current theme background here whenever a theme is
// applied. The main process reads it on the next cold start (see
// startupBackground() in main.js) to paint the window with the right color
// immediately. Only valid hex colors are accepted; everything is wrapped so a
// bad value or a write failure returns false instead of crashing the app.
ipcMain.handle('app:setStartupBg', (_e, color) => {
try {
if (typeof color !== 'string' || !/^#[0-9a-fA-F]{3,8}$/.test(color)) return false;
const cacheFile = path.join(app.getPath('userData'), 'startup.json');
fs.writeFileSync(cacheFile, JSON.stringify({ bg: color }));
return true;
} catch (_err) {
return false;
}
});

// ---- tool updates (ripgrep / fd) ----
ipcMain.handle('tool:checkUpdate', async (_e, params) => {
try { return await toolUpdate.checkUpdate(params || {}); } catch (err) { return { ok: false, error: String((err && err.message) || err) }; }
Expand Down
30 changes: 29 additions & 1 deletion src/main/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,29 @@ let mainWindow = null;
// a race with the window's did-finish-load event.
let initialFolder = null;

// Default theme background (DAYLIGHT `--bg` in src/renderer/js/themes.js). Used
// as the window backgroundColor when no valid startup cache is available.
const DEFAULT_STARTUP_BG = '#f4f4f4';
const HEX_COLOR_RE = /^#[0-9a-fA-F]{3,8}$/;

// Read the cached theme background written by the renderer on the previous run
// (userData/startup.json -> { "bg": "#rrggbb" }). Showing the window with the
// right backgroundColor avoids a white flash before the renderer paints, which
// matters most for dark-theme users. Falls back to the light default theme bg
// when the cache is missing (first launch) or invalid. Wrapped in try/catch so
// a missing file or bad JSON never throws during boot.
function startupBackground() {
try {
const cacheFile = path.join(app.getPath('userData'), 'startup.json');
const raw = fs.readFileSync(cacheFile, 'utf8');
const bg = JSON.parse(raw).bg;
if (typeof bg === 'string' && HEX_COLOR_RE.test(bg)) return bg;
} catch (_e) {
/* no cache yet / unreadable / invalid JSON - use the default */
}
return DEFAULT_STARTUP_BG;
}

function findIcon() {
const candidates = [
// Primary location: LOGO/M2_SCOUT.ico next to the app.
Expand All @@ -87,7 +110,12 @@ function createMainWindow() {
title: `M2_SCOUT v${APP_VERSION}`,
icon: findIcon(),
autoHideMenuBar: true,
backgroundColor: '#f4f4f4',
// Show the window immediately on creation (no `show: false` +
// `ready-to-show` wait) so it appears as early as possible; the renderer
// content fills in right after. backgroundColor is seeded from the cached
// theme bg so dark-theme users don't see a white flash first.
show: true,
backgroundColor: startupBackground(),
webPreferences: {
preload: path.join(__dirname, '..', 'preload', 'preload.js'),
contextIsolation: true,
Expand Down
3 changes: 3 additions & 0 deletions src/preload/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ contextBridge.exposeInMainWorld('m2scout', {
// app events
getCliFolder: () => ipcRenderer.invoke('app:getCliFolder'),
getStartupLogs: () => ipcRenderer.invoke('app:getStartupLogs'),
// Cache the current theme background so the next cold start can paint the
// window with the correct color immediately (avoids a white flash).
setStartupBg: (color) => ipcRenderer.invoke('app:setStartupBg', color),
onCliFolder: (cb) => {
const listener = (_e, payload) => cb(payload);
ipcRenderer.on('app:cliFolder', listener);
Expand Down
7 changes: 7 additions & 0 deletions src/renderer/js/themes.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@
for (const k of Object.keys(vars)) root.style.setProperty(k, vars[k]);
root.setAttribute('data-theme', themeId);
try { localStorage.setItem('appTheme', themeId); } catch (_e) { /* ignore */ }
// Cache the current theme background in the main process so the next cold
// start paints the window with the correct color immediately (no flash).
try {
if (window.m2scout && typeof window.m2scout.setStartupBg === 'function') {
window.m2scout.setStartupBg(vars['--bg']);
}
} catch (_e) { /* ignore */ }
try {
window.dispatchEvent(new CustomEvent('m2-theme-changed', { detail: { theme: themeId } }));
} catch (_e) { /* ignore */ }
Expand Down
Loading