From c176ea801ac2e772fe75d5f88d304df58270c10d Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Mon, 16 Mar 2026 04:15:09 +0100 Subject: [PATCH] feat: squircle dock icon for macOS dev mode macOS only applies the squircle mask to packaged .app bundles. For dev mode, generate a pre-masked icon-dock.png via Swift/AppKit at launch and set it with app.dock.setIcon(). Silently skipped on non-macOS. --- packages/desktop/package.json | 2 +- packages/desktop/scripts/gen-dock-icon.swift | 21 ++++++++++++++++++++ packages/desktop/src/main.ts | 10 ++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 packages/desktop/scripts/gen-dock-icon.swift diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 13789d12..cf614961 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -5,7 +5,7 @@ "main": "dist/main.js", "scripts": { "build:ts": "tsc", - "dev": "tsc && electron .", + "dev": "mkdir -p build && cp ../../icon.png build/icon.png && node -e \"try{require('child_process').execSync('swift scripts/gen-dock-icon.swift',{stdio:'inherit'})}catch(e){}\" && tsc && electron .", "prebuild": "mkdir -p build && cp ../../icon.png build/icon.png", "build": "tsc && electron-builder", "clean": "rm -rf dist dist-electron" diff --git a/packages/desktop/scripts/gen-dock-icon.swift b/packages/desktop/scripts/gen-dock-icon.swift new file mode 100644 index 00000000..0863b33e --- /dev/null +++ b/packages/desktop/scripts/gen-dock-icon.swift @@ -0,0 +1,21 @@ +// Generates a squircle-masked dock icon for macOS dev mode. +// macOS only applies the squircle mask to packaged .app bundles, +// so app.dock.setIcon() needs a pre-masked PNG. + +import AppKit + +let src = NSImage(contentsOfFile: "build/icon.png")! +let size = src.size +let out = NSImage(size: size) + +out.lockFocus() +let radius = size.width * 0.2237 +let path = NSBezierPath(roundedRect: NSRect(origin: .zero, size: size), xRadius: radius, yRadius: radius) +path.addClip() +src.draw(in: NSRect(origin: .zero, size: size), from: NSRect(origin: .zero, size: size), operation: .sourceOver, fraction: 1.0) +out.unlockFocus() + +let tiff = out.tiffRepresentation! +let rep = NSBitmapImageRep(data: tiff)! +let png = rep.representation(using: .png, properties: [:])! +try! png.write(to: URL(fileURLWithPath: "build/icon-dock.png")) diff --git a/packages/desktop/src/main.ts b/packages/desktop/src/main.ts index 3dcc37dd..31c5cc70 100644 --- a/packages/desktop/src/main.ts +++ b/packages/desktop/src/main.ts @@ -513,6 +513,16 @@ if (!gotTheLock) { // ─── App Lifecycle ────────────────────────────────────────────────────────── app.whenReady().then(async () => { + // Set dock icon on macOS (overrides default Electron icon in dev mode) + if (process.platform === 'darwin' && app.dock) { + const dockIcon = path.join(__dirname, '..', 'build', 'icon-dock.png'); + const fallbackIcon = path.join(__dirname, '..', 'build', 'icon.png'); + const iconPath = fs.existsSync(dockIcon) ? dockIcon : fallbackIcon; + if (fs.existsSync(iconPath)) { + app.dock.setIcon(iconPath); + } + } + // macOS application menu with "Change Instance" if (process.platform === 'darwin') { const appMenu = Menu.buildFromTemplate([