From 274852b895e5d0aaa25856bef85b663542dd34ad Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 2 Aug 2026 02:44:24 +0100 Subject: [PATCH] Add macOS vibrancy support and related build scripts - Introduced native macOS window effects with a new dynamic library for enhanced UI. - Updated build scripts to compile the macOS effects library and integrate it into the application. - Modified Electron context to apply macOS-specific styles and effects based on the platform. - Enhanced CSS for macOS vibrancy support in the application layout. - Updated configuration files to include new build steps and dependencies for macOS. --- .gitignore | 5 +- assets/stylesheets/App.css | 37 ++ electrobun.config.ts | 1 + native/macos/window-effects.mm | 402 ++++++++++++++++++ package.json | 3 +- scripts/build-macos-effects.mjs | 48 +++ scripts/pre-build.mjs | 10 + scripts/run-electrobun-build.mjs | 10 + src/components/Dashboard/Layout.jsx | 2 +- .../Dashboard/context/ElectronContext.jsx | 14 +- src/desktop/macos-window-effects.js | 105 +++++ src/desktop/window.js | 8 + 12 files changed, 640 insertions(+), 5 deletions(-) create mode 100644 native/macos/window-effects.mm create mode 100644 scripts/build-macos-effects.mjs create mode 100644 src/desktop/macos-window-effects.js diff --git a/.gitignore b/.gitignore index cc2ac12..3faf97a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,7 @@ stats.html test-results.xml -dist/* \ No newline at end of file +dist/* + +# Native macOS vibrancy (built by scripts/build-macos-effects.mjs) +src/bun/libMacWindowEffects.dylib \ No newline at end of file diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css index d03b350..4c61de8 100644 --- a/assets/stylesheets/App.css +++ b/assets/stylesheets/App.css @@ -108,6 +108,43 @@ user-select: none; } +/* Native macOS vibrancy — transparent web content over NSVisualEffectView */ +html.macos-vibrancy { + --macos-window-corner-radius: 15px; /* keep in sync with MAC_WINDOW_CORNER_RADIUS */ +} + +html.macos-vibrancy, +html.macos-vibrancy body, +html.macos-vibrancy #root { + background: transparent !important; +} + +html.macos-vibrancy .ant-layout.ant-layout-has-sider, +html.macos-vibrancy .main-layout { + background: transparent !important; +} + +html.macos-vibrancy .light-mode .ant-layout-sider { + background: rgba(255, 255, 255, 0.75) !important; +} + +html.macos-vibrancy .dark-mode .ant-layout-sider { + background: rgba(10, 10, 10, 0.65) !important; +} + +html.macos-vibrancy .light-mode .ant-layout-content { + background: transparent !important; +} + +html.macos-vibrancy .dark-mode .ant-layout-content { + background: transparent !important; +} + +html.macos-vibrancy .ant-layout-sider-light, +html.macos-vibrancy .electron-sider .ant-menu-light { + background: transparent !important; +} + .electron-navigation { line-height: 40px; } diff --git a/electrobun.config.ts b/electrobun.config.ts index 79fe3a3..410515d 100644 --- a/electrobun.config.ts +++ b/electrobun.config.ts @@ -42,6 +42,7 @@ export default { }, copy: { "dist/mainview": "views/mainview", + "src/bun/libMacWindowEffects.dylib": "bun/libMacWindowEffects.dylib", }, watch: ["scripts", "src"], watchIgnore: ["dist/**", "build/**", "app_dist/**"], diff --git a/native/macos/window-effects.mm b/native/macos/window-effects.mm new file mode 100644 index 0000000..30e2a0f --- /dev/null +++ b/native/macos/window-effects.mm @@ -0,0 +1,402 @@ +#import + +static NSString *const kElectrobunVibrancyViewIdentifier = + @"ElectrobunVibrancyView"; +static NSString *const kElectrobunNativeDragViewIdentifier = + @"ElectrobunNativeDragView"; +static NSString *const kElectrobunWindowBorderViewIdentifier = + @"ElectrobunWindowBorderView"; + +static CGFloat gWindowCornerRadius = 15.0; + +@interface ElectrobunWindowBorderView : NSView +@property(nonatomic) CGFloat cornerRadius; +@end + +@implementation ElectrobunWindowBorderView +@synthesize cornerRadius = _cornerRadius; + +- (instancetype)initWithFrame:(NSRect)frameRect { + self = [super initWithFrame:frameRect]; + if (self) { + _cornerRadius = gWindowCornerRadius; + } + return self; +} + +- (BOOL)isOpaque { + return NO; +} + +- (BOOL)acceptsFirstMouse:(NSEvent *)event { + (void)event; + return NO; +} + +- (NSView *)hitTest:(NSPoint)point { + (void)point; + return nil; +} + +- (void)setFrame:(NSRect)frame { + [super setFrame:frame]; + [self setNeedsDisplay:YES]; +} + +- (void)drawRect:(NSRect)dirtyRect { + (void)dirtyRect; + + NSRect bounds = [self bounds]; + CGFloat radius = MAX(0.0, self.cornerRadius); + NSBezierPath *borderPath = [NSBezierPath + bezierPathWithRoundedRect:NSInsetRect(bounds, 0.5, 0.5) + xRadius:radius + yRadius:radius]; + + if (@available(macOS 10.14, *)) { + NSAppearance *appearance = [self effectiveAppearance]; + BOOL isDark = + [[appearance bestMatchFromAppearancesWithNames:@[ + NSAppearanceNameAqua, NSAppearanceNameDarkAqua + ]] isEqualToString:NSAppearanceNameDarkAqua]; + + if (isDark) { + [[NSColor colorWithWhite:1.0 alpha:0.18] setStroke]; + } else { + [[NSColor colorWithWhite:1.0 alpha:0.72] setStroke]; + } + } else { + [[NSColor colorWithWhite:1.0 alpha:0.72] setStroke]; + } + + borderPath.lineWidth = 1.0; + [borderPath stroke]; +} +@end + +@interface ElectrobunNativeDragView : NSView +@end + +@implementation ElectrobunNativeDragView +- (BOOL)isOpaque { + return NO; +} + +- (void)drawRect:(NSRect)dirtyRect { + (void)dirtyRect; +} + +- (void)mouseDown:(NSEvent *)event { + NSWindow *window = [self window]; + if (window != nil && event != nil) { + [window performWindowDragWithEvent:event]; + } +} +@end + +static NSVisualEffectView *findVibrancyView(NSView *contentView) { + for (NSView *subview in [contentView subviews]) { + if ([subview isKindOfClass:[NSVisualEffectView class]] && + [[subview identifier] + isEqualToString:kElectrobunVibrancyViewIdentifier]) { + return (NSVisualEffectView *)subview; + } + } + + return nil; +} + +static ElectrobunNativeDragView *findNativeDragView(NSView *contentView) { + for (NSView *subview in [contentView subviews]) { + if ([subview isKindOfClass:[ElectrobunNativeDragView class]] && + [[subview identifier] + isEqualToString:kElectrobunNativeDragViewIdentifier]) { + return (ElectrobunNativeDragView *)subview; + } + } + + return nil; +} + +static ElectrobunWindowBorderView *findWindowBorderView(NSView *contentView) { + for (NSView *subview in [contentView subviews]) { + if ([subview isKindOfClass:[ElectrobunWindowBorderView class]] && + [[subview identifier] + isEqualToString:kElectrobunWindowBorderViewIdentifier]) { + return (ElectrobunWindowBorderView *)subview; + } + } + + return nil; +} + +static void applyWindowCornerRadius(NSWindow *window, CGFloat radius) { + gWindowCornerRadius = MAX(0.0, radius); + + NSView *contentView = [window contentView]; + if (contentView == nil) { + return; + } + + contentView.wantsLayer = YES; + contentView.layer.cornerRadius = gWindowCornerRadius; + contentView.layer.masksToBounds = YES; + + NSVisualEffectView *effectView = findVibrancyView(contentView); + if (effectView != nil) { + effectView.wantsLayer = YES; + effectView.layer.cornerRadius = gWindowCornerRadius; + effectView.layer.masksToBounds = YES; + } + + ElectrobunWindowBorderView *borderView = findWindowBorderView(contentView); + if (borderView != nil) { + borderView.cornerRadius = gWindowCornerRadius; + [borderView setNeedsDisplay:YES]; + } +} + +static void ensureWindowBorder(NSWindow *window) { + NSView *contentView = [window contentView]; + if (contentView == nil) { + return; + } + + ElectrobunWindowBorderView *borderView = findWindowBorderView(contentView); + if (borderView == nil) { + borderView = [[ElectrobunWindowBorderView alloc] + initWithFrame:[contentView bounds]]; + [borderView setIdentifier:kElectrobunWindowBorderViewIdentifier]; + [borderView + setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; + } + + borderView.cornerRadius = gWindowCornerRadius; + [borderView setFrame:[contentView bounds]]; + + if ([borderView superview] == nil) { + [contentView addSubview:borderView + positioned:NSWindowAbove + relativeTo:nil]; + } else { + [borderView removeFromSuperview]; + [contentView addSubview:borderView + positioned:NSWindowAbove + relativeTo:nil]; + } + + [borderView setNeedsDisplay:YES]; +} + +extern "C" bool enableWindowVibrancy(void *windowPtr, double cornerRadius) { + if (windowPtr == nullptr) { + return false; + } + + __block BOOL success = NO; + dispatch_sync(dispatch_get_main_queue(), ^{ + NSWindow *window = (__bridge NSWindow *)windowPtr; + if (![window isKindOfClass:[NSWindow class]]) { + return; + } + + applyWindowCornerRadius(window, (CGFloat)cornerRadius); + + [window setOpaque:NO]; + [window setBackgroundColor:[NSColor clearColor]]; + [window setTitlebarAppearsTransparent:YES]; + [window setHasShadow:YES]; + + NSView *contentView = [window contentView]; + if (contentView == nil) { + return; + } + + NSVisualEffectView *effectView = findVibrancyView(contentView); + + if (effectView == nil) { + effectView = [[NSVisualEffectView alloc] + initWithFrame:[contentView bounds]]; + [effectView setIdentifier:kElectrobunVibrancyViewIdentifier]; + [effectView + setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; + } + + if (@available(macOS 10.14, *)) { + [effectView setMaterial:NSVisualEffectMaterialUnderWindowBackground]; + } else { + [effectView setMaterial:NSVisualEffectMaterialSidebar]; + } + [effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow]; + [effectView setState:NSVisualEffectStateActive]; + + if ([effectView superview] == nil) { + NSView *relativeView = [[contentView subviews] firstObject]; + if (relativeView != nil) { + [contentView addSubview:effectView + positioned:NSWindowBelow + relativeTo:relativeView]; + } else { + [contentView addSubview:effectView]; + } + } + + ensureWindowBorder(window); + applyWindowCornerRadius(window, gWindowCornerRadius); + + [window invalidateShadow]; + success = YES; + }); + + return success; +} + +extern "C" bool setWindowCornerRadius(void *windowPtr, double cornerRadius) { + if (windowPtr == nullptr) { + return false; + } + + __block BOOL success = NO; + dispatch_sync(dispatch_get_main_queue(), ^{ + NSWindow *window = (__bridge NSWindow *)windowPtr; + if (![window isKindOfClass:[NSWindow class]]) { + return; + } + + applyWindowCornerRadius(window, (CGFloat)cornerRadius); + ensureWindowBorder(window); + [window invalidateShadow]; + success = YES; + }); + + return success; +} + +extern "C" bool ensureWindowShadow(void *windowPtr) { + if (windowPtr == nullptr) { + return false; + } + + __block BOOL success = NO; + dispatch_sync(dispatch_get_main_queue(), ^{ + NSWindow *window = (__bridge NSWindow *)windowPtr; + if (![window isKindOfClass:[NSWindow class]]) { + return; + } + + [window setHasShadow:YES]; + [window invalidateShadow]; + ensureWindowBorder(window); + applyWindowCornerRadius(window, gWindowCornerRadius); + success = YES; + }); + + return success; +} + +extern "C" bool setWindowTrafficLightsPosition(void *windowPtr, double x, + double yFromTop) { + if (windowPtr == nullptr) { + return false; + } + + __block BOOL success = NO; + dispatch_sync(dispatch_get_main_queue(), ^{ + NSWindow *window = (__bridge NSWindow *)windowPtr; + if (![window isKindOfClass:[NSWindow class]]) { + return; + } + + NSButton *closeButton = + [window standardWindowButton:NSWindowCloseButton]; + NSButton *minimizeButton = + [window standardWindowButton:NSWindowMiniaturizeButton]; + NSButton *zoomButton = [window standardWindowButton:NSWindowZoomButton]; + + if (closeButton == nil || minimizeButton == nil || zoomButton == nil) { + return; + } + + NSView *buttonContainer = [closeButton superview]; + if (buttonContainer == nil) { + return; + } + + CGFloat spacing = NSMinX(minimizeButton.frame) - NSMinX(closeButton.frame); + if (spacing <= 0) { + spacing = closeButton.frame.size.width + 6.0; + } + + BOOL flipped = [buttonContainer isFlipped]; + CGFloat targetY = yFromTop; + if (!flipped) { + targetY = buttonContainer.frame.size.height - yFromTop - + closeButton.frame.size.height; + } + targetY = MAX(0.0, targetY); + + CGFloat currentX = x; + NSArray *buttons = @[ closeButton, minimizeButton, zoomButton ]; + for (NSButton *button in buttons) { + [button setFrameOrigin:NSMakePoint(currentX, targetY)]; + currentX += spacing; + } + + [buttonContainer setNeedsLayout:YES]; + [buttonContainer layoutSubtreeIfNeeded]; + [window invalidateShadow]; + success = YES; + }); + + return success; +} + +extern "C" bool setNativeWindowDragRegion(void *windowPtr, double x, + double height) { + if (windowPtr == nullptr) { + return false; + } + + __block BOOL success = NO; + dispatch_sync(dispatch_get_main_queue(), ^{ + NSWindow *window = (__bridge NSWindow *)windowPtr; + if (![window isKindOfClass:[NSWindow class]]) { + return; + } + + NSView *contentView = [window contentView]; + if (contentView == nil) { + return; + } + + CGFloat dragX = MAX(0.0, x); + CGFloat dragHeight = MAX(0.0, height); + CGFloat dragWidth = MAX(0.0, contentView.bounds.size.width - dragX); + if (dragHeight <= 0.0 || dragWidth <= 0.0) { + return; + } + + BOOL flipped = [contentView isFlipped]; + CGFloat dragY = flipped ? 0.0 : contentView.bounds.size.height - dragHeight; + dragY = MAX(0.0, dragY); + + ElectrobunNativeDragView *dragView = findNativeDragView(contentView); + if (dragView == nil) { + dragView = [[ElectrobunNativeDragView alloc] initWithFrame:NSZeroRect]; + [dragView setIdentifier:kElectrobunNativeDragViewIdentifier]; + } + + [dragView setFrame:NSMakeRect(dragX, dragY, dragWidth, dragHeight)]; + [dragView setAutoresizingMask:NSViewWidthSizable]; + + if ([dragView superview] == nil) { + [contentView addSubview:dragView + positioned:NSWindowAbove + relativeTo:nil]; + } + + success = YES; + }); + + return success; +} diff --git a/package.json b/package.json index 44ca13e..31d720d 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,8 @@ "build:electron": "vite build && electron-builder", "build:cloudflare": "cross-env VITE_DEPLOY_TARGET=cloudflare vite build", "deploy": "npm run build:cloudflare && wrangler pages deploy --branch main", - "postinstall": "bun scripts/patch-electrobun-src.mjs", + "postinstall": "bun scripts/patch-electrobun-src.mjs && bun scripts/build-macos-effects.mjs", + "build:macos-effects": "bun scripts/build-macos-effects.mjs", "clean": "bun scripts/clean-build.mjs" }, "eslintConfig": { diff --git a/scripts/build-macos-effects.mjs b/scripts/build-macos-effects.mjs new file mode 100644 index 0000000..1398095 --- /dev/null +++ b/scripts/build-macos-effects.mjs @@ -0,0 +1,48 @@ +import { existsSync, mkdirSync, statSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const srcFile = path.join(rootDir, "native/macos/window-effects.mm"); +const outFile = path.join(rootDir, "src/bun/libMacWindowEffects.dylib"); + +function createPlaceholder() { + mkdirSync(path.dirname(outFile), { recursive: true }); + writeFileSync(outFile, ""); + console.log(`build-macos-effects: created placeholder dylib at ${outFile}`); +} + +if (process.platform !== "darwin") { + createPlaceholder(); + process.exit(0); +} + +if (!existsSync(srcFile)) { + console.error(`build-macos-effects: missing source file ${srcFile}`); + process.exit(1); +} + +mkdirSync(path.dirname(outFile), { recursive: true }); + +const result = spawnSync( + "xcrun", + [ + "clang++", + "-dynamiclib", + "-fobjc-arc", + "-framework", + "Cocoa", + srcFile, + "-o", + outFile, + ], + { cwd: rootDir, stdio: "inherit" }, +); + +if (result.status !== 0) { + process.exit(result.status ?? 1); +} + +const { size } = statSync(outFile); +console.log(`build-macos-effects: built ${outFile} (${size} bytes)`); diff --git a/scripts/pre-build.mjs b/scripts/pre-build.mjs index cc518d7..e6717a7 100644 --- a/scripts/pre-build.mjs +++ b/scripts/pre-build.mjs @@ -36,6 +36,16 @@ if (buildEnv === "stable" && process.env.ELECTROBUN_OS === "macos") { } } +const buildMacosEffects = spawnSync( + "bun", + [path.join(rootDir, "scripts/build-macos-effects.mjs")], + { cwd: rootDir, stdio: "inherit", env: process.env }, +); + +if (buildMacosEffects.status !== 0) { + process.exit(buildMacosEffects.status ?? 1); +} + const prepareIcons = spawnSync( "bun", [ diff --git a/scripts/run-electrobun-build.mjs b/scripts/run-electrobun-build.mjs index 280f537..29be7f0 100644 --- a/scripts/run-electrobun-build.mjs +++ b/scripts/run-electrobun-build.mjs @@ -26,6 +26,16 @@ if (patchResult.status !== 0) { process.exit(patchResult.status ?? 1); } +const macosEffectsResult = spawnSync( + "bun", + [path.join(rootDir, "scripts/build-macos-effects.mjs")], + { cwd: rootDir, stdio: "inherit", env: process.env }, +); + +if (macosEffectsResult.status !== 0) { + process.exit(macosEffectsResult.status ?? 1); +} + const ensureCoreEnv = { ...process.env, ELECTROBUN_TARGET_ARCH: targetArch, diff --git a/src/components/Dashboard/Layout.jsx b/src/components/Dashboard/Layout.jsx index 56160aa..9c20998 100644 --- a/src/components/Dashboard/Layout.jsx +++ b/src/components/Dashboard/Layout.jsx @@ -30,7 +30,7 @@ const DashboardLayout = ({ children }) => { diff --git a/src/components/Dashboard/context/ElectronContext.jsx b/src/components/Dashboard/context/ElectronContext.jsx index 858746b..a2886c2 100644 --- a/src/components/Dashboard/context/ElectronContext.jsx +++ b/src/components/Dashboard/context/ElectronContext.jsx @@ -118,7 +118,12 @@ const ElectronProvider = ({ children }) => { if (useElectrobun) { desktopAPI.getOsInfo().then((info) => { - if (info?.platform) setPlatform(info.platform) + if (info?.platform) { + setPlatform(info.platform) + if (info.platform === 'darwin') { + document.documentElement.classList.add('macos-vibrancy') + } + } }) desktopAPI.getWindowState().then(applyWindowState) @@ -142,7 +147,12 @@ const ElectronProvider = ({ children }) => { if (!ipcRenderer) return ipcRenderer.invoke('os-info').then((info) => { - if (info?.platform) setPlatform(info.platform) + if (info?.platform) { + setPlatform(info.platform) + if (info.platform === 'darwin') { + document.documentElement.classList.add('macos-vibrancy') + } + } }) ipcRenderer.invoke('window-state').then(applyWindowState) diff --git a/src/desktop/macos-window-effects.js b/src/desktop/macos-window-effects.js new file mode 100644 index 0000000..d715dff --- /dev/null +++ b/src/desktop/macos-window-effects.js @@ -0,0 +1,105 @@ +import { dlopen, FFIType } from 'bun:ffi' +import { existsSync, statSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const MAC_TRAFFIC_LIGHTS_X = 14 +const MAC_TRAFFIC_LIGHTS_Y = 12 +export const MAC_WINDOW_CORNER_RADIUS = 15 + +function resolveDylibPath() { + const moduleDir = path.dirname(fileURLToPath(import.meta.url)) + const candidates = [ + path.join(moduleDir, '../bun/libMacWindowEffects.dylib'), + path.join(moduleDir, 'libMacWindowEffects.dylib') + ] + + for (const candidate of candidates) { + if (!existsSync(candidate)) { + continue + } + + try { + if (statSync(candidate).size > 0) { + return candidate + } + } catch { + // Ignore unreadable paths and keep searching. + } + } + + return null +} + +export function applyMacOSWindowEffects(mainWindow) { + if (process.platform !== 'darwin' || !mainWindow?.ptr) { + return + } + + const dylibPath = resolveDylibPath() + if (!dylibPath) { + console.warn( + 'macOS vibrancy: native effects library not found; using transparent window only.' + ) + return + } + + try { + const lib = dlopen(dylibPath, { + enableWindowVibrancy: { + args: [FFIType.ptr, FFIType.f64], + returns: FFIType.bool + }, + ensureWindowShadow: { + args: [FFIType.ptr], + returns: FFIType.bool + }, + setWindowCornerRadius: { + args: [FFIType.ptr, FFIType.f64], + returns: FFIType.bool + }, + setWindowTrafficLightsPosition: { + args: [FFIType.ptr, FFIType.f64, FFIType.f64], + returns: FFIType.bool + } + }) + + const vibrancyEnabled = lib.symbols.enableWindowVibrancy( + mainWindow.ptr, + MAC_WINDOW_CORNER_RADIUS + ) + const shadowEnabled = lib.symbols.ensureWindowShadow(mainWindow.ptr) + const alignButtons = () => + lib.symbols.setWindowTrafficLightsPosition( + mainWindow.ptr, + MAC_TRAFFIC_LIGHTS_X, + MAC_TRAFFIC_LIGHTS_Y + ) + const refreshWindowChrome = () => { + lib.symbols.setWindowCornerRadius( + mainWindow.ptr, + MAC_WINDOW_CORNER_RADIUS + ) + alignButtons() + } + + const buttonsAlignedNow = alignButtons() + + setTimeout(() => { + refreshWindowChrome() + }, 120) + + mainWindow.on?.('resize', () => { + refreshWindowChrome() + }) + + console.log( + `macOS vibrancy applied (vibrancy=${vibrancyEnabled}, shadow=${shadowEnabled}, trafficLights=${buttonsAlignedNow}, cornerRadius=${MAC_WINDOW_CORNER_RADIUS})` + ) + } catch (error) { + console.warn( + 'macOS vibrancy: failed to apply native window effects:', + error + ) + } +} diff --git a/src/desktop/window.js b/src/desktop/window.js index b01db6b..34e2d33 100644 --- a/src/desktop/window.js +++ b/src/desktop/window.js @@ -1,7 +1,10 @@ import Electrobun, { BrowserWindow, Updater, Utils } from "electrobun/bun"; import { applyApplicationMenu, setupApplicationMenuEvents } from "./menu.js"; +import { applyMacOSWindowEffects } from "./macos-window-effects.js"; import { sendToRenderer, setMessageSender } from "./notify.js"; +const isMacOS = process.platform === "darwin"; + const DEV_SERVER_PORT = 5780; const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`; const PROTOCOL_PREFIX = "farmcontrol://"; @@ -127,6 +130,7 @@ export async function createMainWindow(rpc) { rpc, titleBarStyle: "hiddenInset", trafficLightOffset: { x: 14, y: 12 }, + ...(isMacOS ? { transparent: true } : {}), frame: { width: 1200, height: 800, @@ -135,6 +139,10 @@ export async function createMainWindow(rpc) { }, }); + if (isMacOS) { + applyMacOSWindowEffects(mainWindow); + } + setupMainWindowMessaging(mainWindow); applyApplicationMenu(); setupApplicationMenuEvents({