From cd2f1e52ef22faae8444b4048675d9d9202cf819 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Mon, 16 Mar 2026 04:29:09 +0100 Subject: [PATCH] fix: proper macOS dock icon via .icns bundle replacement Replace the Swift squircle hack with the correct Apple approach: generate a proper .icns using sips + iconutil, then replace electron.icns in the Electron bundle before launch. macOS applies its native squircle mask + shadow from the .icns automatically. --- packages/desktop/package.json | 2 +- packages/desktop/scripts/gen-dock-icon.swift | 21 ------------------- packages/desktop/scripts/gen-icns.sh | 22 ++++++++++++++++++++ packages/desktop/src/main.ts | 10 --------- 4 files changed, 23 insertions(+), 32 deletions(-) delete mode 100644 packages/desktop/scripts/gen-dock-icon.swift create mode 100755 packages/desktop/scripts/gen-icns.sh diff --git a/packages/desktop/package.json b/packages/desktop/package.json index cf614961..8b134060 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -5,7 +5,7 @@ "main": "dist/main.js", "scripts": { "build:ts": "tsc", - "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 .", + "dev": "mkdir -p build && cp ../../icon.png build/icon.png && bash scripts/gen-icns.sh && cp build/icon.icns $(find ../../node_modules -path '*/electron/dist/Electron.app/Contents/Resources/electron.icns' 2>/dev/null | head -1) 2>/dev/null; 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 deleted file mode 100644 index 0863b33e..00000000 --- a/packages/desktop/scripts/gen-dock-icon.swift +++ /dev/null @@ -1,21 +0,0 @@ -// 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/scripts/gen-icns.sh b/packages/desktop/scripts/gen-icns.sh new file mode 100755 index 00000000..57212977 --- /dev/null +++ b/packages/desktop/scripts/gen-icns.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Generates a proper macOS .icns from the master icon PNG using Apple's iconutil. +# macOS applies squircle mask + shadow natively from .icns — no manual masking needed. +set -e + +SRC="build/icon.png" +ICONSET="build/icon.iconset" + +mkdir -p "$ICONSET" +sips -z 16 16 "$SRC" --out "$ICONSET/icon_16x16.png" >/dev/null +sips -z 32 32 "$SRC" --out "$ICONSET/icon_16x16@2x.png" >/dev/null +sips -z 32 32 "$SRC" --out "$ICONSET/icon_32x32.png" >/dev/null +sips -z 64 64 "$SRC" --out "$ICONSET/icon_32x32@2x.png" >/dev/null +sips -z 128 128 "$SRC" --out "$ICONSET/icon_128x128.png" >/dev/null +sips -z 256 256 "$SRC" --out "$ICONSET/icon_128x128@2x.png" >/dev/null +sips -z 256 256 "$SRC" --out "$ICONSET/icon_256x256.png" >/dev/null +sips -z 512 512 "$SRC" --out "$ICONSET/icon_256x256@2x.png" >/dev/null +sips -z 512 512 "$SRC" --out "$ICONSET/icon_512x512.png" >/dev/null +sips -z 1024 1024 "$SRC" --out "$ICONSET/icon_512x512@2x.png" >/dev/null + +iconutil -c icns "$ICONSET" -o build/icon.icns +rm -rf "$ICONSET" diff --git a/packages/desktop/src/main.ts b/packages/desktop/src/main.ts index 31c5cc70..3dcc37dd 100644 --- a/packages/desktop/src/main.ts +++ b/packages/desktop/src/main.ts @@ -513,16 +513,6 @@ 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([