fix(electron): sync title bar button colors with app theme

Update window control buttons (minimize, maximize, close) to match
the app's dark/light theme setting instead of always using white.

Fixes #5676
This commit is contained in:
johannesjo 2025-12-07 09:09:49 +01:00
parent 7c14bb067b
commit eefbc7135a
5 changed files with 40 additions and 1 deletions

View file

@ -120,6 +120,8 @@ export interface ElectronAPI {
sendSettingsUpdate(globalCfg: GlobalConfigState): void;
updateTitleBarDarkMode(isDarkMode: boolean): void;
registerGlobalShortcuts(keyboardConfig: KeyboardConfig): void;
showFullScreenBlocker(args: { msg?: string; takeABreakCfg: TakeABreakConfig }): void;

View file

@ -7,6 +7,7 @@ import {
Menu,
MenuItem,
MenuItemConstructorOptions,
nativeTheme,
shell,
} from 'electron';
import { errorHandlerWithFrontendInform } from './error-handler-with-frontend-inform';
@ -85,11 +86,13 @@ export const createWindow = async ({
persistedIsUseCustomWindowTitleBar ?? legacyIsUseObsidianStyleHeader ?? true;
const titleBarStyle: BrowserWindowConstructorOptions['titleBarStyle'] =
isUseCustomWindowTitleBar || IS_MAC ? 'hidden' : 'default';
// Determine initial symbol color based on system theme preference
const initialSymbolColor = nativeTheme.shouldUseDarkColors ? '#fff' : '#000';
const titleBarOverlay: BrowserWindowConstructorOptions['titleBarOverlay'] =
isUseCustomWindowTitleBar && !IS_MAC
? {
color: '#00000000',
symbolColor: '#fff',
symbolColor: initialSymbolColor,
height: 44,
}
: undefined;
@ -221,6 +224,23 @@ export const createWindow = async ({
mainWinModule.isAppReady = true;
});
// Listen for theme changes to update title bar overlay symbol color
if (isUseCustomWindowTitleBar && !IS_MAC) {
ipcMain.on(IPC.UPDATE_TITLE_BAR_DARK_MODE, (ev, isDarkMode: boolean) => {
try {
const symbolColor = isDarkMode ? '#fff' : '#000';
mainWin.setTitleBarOverlay({
color: '#00000000',
symbolColor,
height: 44,
});
} catch (e) {
// setTitleBarOverlay may not be available on all platforms
log('Failed to update title bar overlay:', e);
}
});
}
return mainWin;
};

View file

@ -99,6 +99,8 @@ const ea: ElectronAPI = {
sendAppSettingsToElectron: (globalCfg) =>
_send('TRANSFER_SETTINGS_TO_ELECTRON', globalCfg),
sendSettingsUpdate: (globalCfg) => _send('UPDATE_SETTINGS', globalCfg),
updateTitleBarDarkMode: (isDarkMode: boolean) =>
_send('UPDATE_TITLE_BAR_DARK_MODE', isDarkMode),
registerGlobalShortcuts: (keyboardCfg) =>
_send('REGISTER_GLOBAL_SHORTCUTS', keyboardCfg),
showFullScreenBlocker: (args) => _send('FULL_SCREEN_BLOCKER', args),

View file

@ -69,6 +69,7 @@ export enum IPC {
PLUGIN_EXEC_NODE_SCRIPT = 'PLUGIN_EXEC_NODE_SCRIPT',
UPDATE_SETTINGS = 'UPDATE_SETTINGS',
UPDATE_TITLE_BAR_DARK_MODE = 'UPDATE_TITLE_BAR_DARK_MODE',
// maybe_UPDATE_CURRENT_TASK = 'UPDATE_CURRENT_TASK',
// maybe_IS_IDLE = 'IS_IDLE',

View file

@ -5,6 +5,7 @@ import {
Injectable,
runInInjectionContext,
signal,
untracked,
} from '@angular/core';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import { BodyClass, IS_ELECTRON } from '../../app.constants';
@ -231,6 +232,19 @@ export class GlobalThemeService {
this._setColorTheme(theme),
);
this._isDarkThemeObs$.subscribe((isDarkTheme) => this._setDarkTheme(isDarkTheme));
// Update Electron title bar overlay when dark mode changes
if (IS_ELECTRON && !IS_MAC) {
effect(() => {
const isDark = this.isDarkTheme();
// Use untracked to prevent reading misc from creating a dependency
const misc = untracked(() => this._globalConfigService.misc());
// Only update if custom window title bar is enabled
if (misc?.isUseCustomWindowTitleBar !== false) {
window.ea.updateTitleBarDarkMode(isDark);
}
});
}
}
private _initHandlersForInitialBodyClasses(): void {