From 4a9b3255324bb176e1b4eaa687f7c8389aa1bbdb Mon Sep 17 00:00:00 2001 From: Boof2015 <75185879+Boof2015@users.noreply.github.com> Date: Fri, 10 Jul 2026 03:24:43 -0400 Subject: [PATCH] fix blur on macos --- src/main/index.ts | 29 +++++++++++++++++++++++++---- src/shared/windowCapabilities.ts | 6 ++++++ test/renderer-helpers.test.ts | 13 ++++++++++++- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 1f2401c..11f6c52 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -23,7 +23,10 @@ import { RESIZE_DIRECTIONS, type ResizeDirection } from '../types/windowResize' import type { DialogOptions, DialogResult } from '../types/dialog' import { normalizeProfile } from '../shared/profileState' import { resolveNativeThemeSource } from '../shared/themeState' -import { resolveWindowCapabilities } from '../shared/windowCapabilities' +import { + resolveMacWindowBlurMaterial, + resolveWindowCapabilities, +} from '../shared/windowCapabilities' import { clampDraggedMainWindowBounds, clampRestoredWindowBounds, @@ -495,6 +498,7 @@ function applyNativeThemeSnapshot(snapshot: ThemeLibrarySnapshot): void { ? snapshot.themes[snapshot.activeThemeId] ?? null : null nativeTheme.themeSource = resolveNativeThemeSource(activeTheme) + refreshMacBlurVibrancy() } async function syncNativeThemeAppearance(): Promise { @@ -841,8 +845,9 @@ function getWindowBackgroundSnapshot(): WindowBackgroundSnapshot { // intact. On Windows, blurred and clear windows must be created `transparent` // (blurred composites accent-policy acrylic behind the alpha pixels), which is // mutually exclusive with the thick frame — those windows fall back to the JS -// move/resize controllers. On macOS, blurred uses vibrancy and keeps the -// native frame semantics. +// move/resize controllers. On macOS, blurred uses theme-aware native vibrancy +// (HUD for dark themes, Content for light themes) and keeps the native frame +// semantics. function getFramelessWindowOptions( background: WindowBackgroundState = SOLID_WINDOW_BACKGROUND, ): BrowserWindowConstructorOptions { @@ -861,7 +866,7 @@ function getFramelessWindowOptions( : {}), ...(process.platform === 'darwin' && background.mode === 'blurred' ? { - vibrancy: 'under-window' as const, + vibrancy: resolveMacWindowBlurMaterial(nativeTheme.shouldUseDarkColors), visualEffectState: 'active' as const, } : {}), @@ -886,6 +891,22 @@ function getBackgroundCapableWindows(): BrowserWindow[] { return windows } +function refreshMacBlurVibrancy(): void { + if (process.platform !== 'darwin' || getEffectiveWindowBackground().mode !== 'blurred') { + return + } + + const material = resolveMacWindowBlurMaterial(nativeTheme.shouldUseDarkColors) + for (const window of getBackgroundCapableWindows()) { + try { + window.setVibrancy(material) + } catch { + // A material refresh is cosmetic; keep the current backdrop if AppKit + // rejects a live update during a window transition. + } + } +} + function broadcastWindowBackgroundChanged(snapshot: WindowBackgroundSnapshot): void { for (const window of getBackgroundCapableWindows()) { if (!window.webContents.isDestroyed()) { diff --git a/src/shared/windowCapabilities.ts b/src/shared/windowCapabilities.ts index c2cfc95..2609ff4 100644 --- a/src/shared/windowCapabilities.ts +++ b/src/shared/windowCapabilities.ts @@ -1,5 +1,11 @@ import type { WindowCapabilities, WindowDisplayServer } from '../types/windowCapabilities' +export type MacWindowBlurMaterial = 'hud' | 'content' + +export function resolveMacWindowBlurMaterial(useDarkColors: boolean): MacWindowBlurMaterial { + return useDarkColors ? 'hud' : 'content' +} + interface WindowCapabilityResolutionOptions { platform: string argv?: readonly string[] diff --git a/test/renderer-helpers.test.ts b/test/renderer-helpers.test.ts index 38146f1..b942731 100644 --- a/test/renderer-helpers.test.ts +++ b/test/renderer-helpers.test.ts @@ -28,7 +28,10 @@ import { import { createDefaultProfile, } from '../src/shared/profileState' -import { resolveWindowCapabilities } from '../src/shared/windowCapabilities' +import { + resolveMacWindowBlurMaterial, + resolveWindowCapabilities, +} from '../src/shared/windowCapabilities' import { clampDraggedMainWindowBounds, clampRestoredWindowBounds, @@ -3225,6 +3228,11 @@ test('resolveWindowCapabilities gates blurred backgrounds on the Windows 11 22H2 assert.equal(resolveForBuild('not-a-version'), false) }) +test('resolveMacWindowBlurMaterial selects neutral theme-aware macOS materials', () => { + assert.equal(resolveMacWindowBlurMaterial(true), 'hud') + assert.equal(resolveMacWindowBlurMaterial(false), 'content') +}) + test('Wayland window controls use native drag regions and omit unsupported reposition/geometry paths', async () => { const toolbarSource = await readFile(join(process.cwd(), 'src', 'renderer', 'components', 'Toolbar.tsx'), 'utf8') const appSource = await readFile(join(process.cwd(), 'src', 'renderer', 'App.tsx'), 'utf8') @@ -3249,6 +3257,9 @@ test('main and detached windows keep frameless Prism chrome while enabling snap- const nowPlayingSource = await readFile(join(process.cwd(), 'src', 'renderer', 'components', 'NowPlayingConfigWindow.tsx'), 'utf8') assert.match(mainSource, /function getFramelessWindowOptions\([\s\S]*?return \{[\s\S]*?frame: false,[\s\S]*?transparent: background\.mode === 'clear' \|\| transparentOnWindows,[\s\S]*?roundedCorners: false,[\s\S]*?hasShadow: false,[\s\S]*?thickFrame: background\.mode === 'solid',[\s\S]*?backgroundMaterial: 'none'[\s\S]*?resizable: true,[\s\S]*?maximizable: true,[\s\S]*?fullscreenable: true,[\s\S]*?skipTaskbar: false,[\s\S]*?\}/) + assert.match(mainSource, /process\.platform === 'darwin' && background\.mode === 'blurred'[\s\S]*?vibrancy: resolveMacWindowBlurMaterial\(nativeTheme\.shouldUseDarkColors\),[\s\S]*?visualEffectState: 'active'/) + assert.match(mainSource, /function applyNativeThemeSnapshot\([\s\S]*?nativeTheme\.themeSource = resolveNativeThemeSource\(activeTheme\)[\s\S]*?refreshMacBlurVibrancy\(\)/) + assert.match(mainSource, /function refreshMacBlurVibrancy\(\): void \{[\s\S]*?process\.platform !== 'darwin'[\s\S]*?getEffectiveWindowBackground\(\)\.mode !== 'blurred'[\s\S]*?resolveMacWindowBlurMaterial\(nativeTheme\.shouldUseDarkColors\)[\s\S]*?getBackgroundCapableWindows\(\)[\s\S]*?window\.setVibrancy\(material\)/) assert.match(mainSource, /function createMainWindow\(restoreBounds\?: WindowBounds\): void \{[\s\S]*?\.\.\.getFramelessWindowOptions\(background\),/) assert.match(mainSource, /function createScopePopoutWindow\(kind: ScopeKind, rawBounds\?: WindowBounds\): BrowserWindow \| null \{[\s\S]*?\.\.\.getFramelessWindowOptions\(background\),/) assert.match(mainSource, /function createNowPlayingConfigWindow\(\): BrowserWindow \{[\s\S]*?\.\.\.getFramelessWindowOptions\(\),/)