diff --git a/packages/electron/package.json b/packages/electron/package.json index 468cd147..c5ad1c43 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -27,6 +27,7 @@ "build:web-assets": "node ./scripts/build-web-assets.mjs", "build": "bun -e \"process.exit(0)\"", "bundle:main": "bun ./scripts/bundle-main.mjs", + "generate:macos-icon": "node ./scripts/generate-macos-icon-assets.cjs", "rebuild:native": "node ./scripts/rebuild-native.mjs", "package": "bun run build:web-assets && bun run bundle:main && bun run rebuild:native && electron-builder", "finalize:latest-yml": "node ./scripts/finalize-latest-yml.mjs", @@ -46,6 +47,7 @@ "to": "web-dist" } ], + "afterPack": "scripts/after-pack.cjs", "directories": { "buildResources": "resources/icons", "output": "dist" @@ -55,6 +57,9 @@ "mac": { "category": "public.app-category.developer-tools", "icon": "resources/icons/icon.icns", + "extendInfo": { + "CFBundleIconName": "AppIcon" + }, "hardenedRuntime": true, "gatekeeperAssess": false, "entitlements": "resources/entitlements.mac.plist", diff --git a/packages/electron/resources/icons/AppIcon.icon/Assets/app-icon-glyph-dark 4.png b/packages/electron/resources/icons/AppIcon.icon/Assets/app-icon-glyph-dark 4.png new file mode 100644 index 00000000..48a96de3 Binary files /dev/null and b/packages/electron/resources/icons/AppIcon.icon/Assets/app-icon-glyph-dark 4.png differ diff --git a/packages/electron/resources/icons/AppIcon.icon/Assets/app-icon-glyph-light 2.png b/packages/electron/resources/icons/AppIcon.icon/Assets/app-icon-glyph-light 2.png new file mode 100644 index 00000000..1aac2314 Binary files /dev/null and b/packages/electron/resources/icons/AppIcon.icon/Assets/app-icon-glyph-light 2.png differ diff --git a/packages/electron/resources/icons/AppIcon.icon/icon.json b/packages/electron/resources/icons/AppIcon.icon/icon.json new file mode 100644 index 00000000..43099912 --- /dev/null +++ b/packages/electron/resources/icons/AppIcon.icon/icon.json @@ -0,0 +1,114 @@ +{ + "fill-specializations" : [ + { + "value" : "automatic" + }, + { + "appearance" : "dark", + "value" : "automatic" + } + ], + "groups" : [ + { + "layers" : [ + { + "glass" : false, + "image-name" : "app-icon-glyph-dark 4.png", + "name" : "app-icon-glyph-dark 4", + "opacity-specializations" : [ + { + "appearance" : "light", + "value" : 0 + }, + { + "appearance" : "dark", + "value" : 1 + }, + { + "appearance" : "tinted", + "value" : 1 + } + ], + "position-specializations" : [ + { + "idiom" : "square", + "value" : { + "scale" : 1.15, + "translation-in-points" : [ + -0.6499999999998636, + -3.25 + ] + } + } + ] + }, + { + "blend-mode" : "normal", + "fill-specializations" : [ + { + "value" : "automatic" + }, + { + "appearance" : "dark", + "value" : "automatic" + } + ], + "glass" : false, + "hidden-specializations" : [ + { + "idiom" : "square", + "value" : false + } + ], + "image-name" : "app-icon-glyph-light 2.png", + "name" : "app-icon-glyph-light 2", + "opacity-specializations" : [ + { + "appearance" : "dark", + "value" : 0 + }, + { + "appearance" : "tinted", + "value" : 0 + } + ], + "position-specializations" : [ + { + "value" : { + "scale" : 1.4, + "translation-in-points" : [ + 0, + -5.2000000000000455 + ] + } + }, + { + "idiom" : "square", + "value" : { + "scale" : 1.3, + "translation-in-points" : [ + 0, + -5.2000000000000455 + ] + } + } + ] + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/packages/electron/resources/icons/Assets.car b/packages/electron/resources/icons/Assets.car new file mode 100644 index 00000000..3c9106c6 Binary files /dev/null and b/packages/electron/resources/icons/Assets.car differ diff --git a/packages/electron/scripts/after-pack.cjs b/packages/electron/scripts/after-pack.cjs new file mode 100644 index 00000000..c68f53fb --- /dev/null +++ b/packages/electron/scripts/after-pack.cjs @@ -0,0 +1,17 @@ +const fs = require('node:fs'); +const path = require('node:path'); + +module.exports = (context) => { + if (context.electronPlatformName !== 'darwin') return; + + const appName = context.packager.appInfo.productFilename; + const appBundlePath = path.join(context.appOutDir, `${appName}.app`); + const resourcesPath = path.join(appBundlePath, 'Contents', 'Resources'); + const sourceAssetsPath = path.join(__dirname, '..', 'resources', 'icons', 'Assets.car'); + + if (!fs.existsSync(sourceAssetsPath)) { + throw new Error(`Missing compiled app icon asset catalog at ${sourceAssetsPath}`); + } + + fs.copyFileSync(sourceAssetsPath, path.join(resourcesPath, 'Assets.car')); +}; diff --git a/packages/electron/scripts/generate-macos-icon-assets.cjs b/packages/electron/scripts/generate-macos-icon-assets.cjs new file mode 100644 index 00000000..13cba369 --- /dev/null +++ b/packages/electron/scripts/generate-macos-icon-assets.cjs @@ -0,0 +1,46 @@ +const { execFileSync } = require('node:child_process'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); + +const iconName = 'AppIcon'; +const iconsDir = path.join(__dirname, '..', 'resources', 'icons'); +const sourceIconPath = path.join(iconsDir, `${iconName}.icon`); +const outputAssetsPath = path.join(iconsDir, 'Assets.car'); + +const resolveActool = () => { + try { + return execFileSync('xcrun', ['--find', 'actool'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); + } catch { + const xcodeActool = '/Applications/Xcode.app/Contents/Developer/usr/bin/actool'; + if (fs.existsSync(xcodeActool)) return xcodeActool; + throw new Error('Unable to find actool. Install Xcode or set DEVELOPER_DIR to a full Xcode installation.'); + } +}; + +if (!fs.existsSync(sourceIconPath)) { + throw new Error(`Missing Icon Composer source at ${sourceIconPath}`); +} + +const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-app-icon-')); +try { + execFileSync(resolveActool(), [ + sourceIconPath, + '--compile', tmpDir, + '--app-icon', iconName, + '--platform', 'macosx', + '--target-device', 'mac', + '--minimum-deployment-target', '13.0', + '--output-partial-info-plist', path.join(tmpDir, 'partial.plist'), + ], { stdio: 'inherit' }); + + const generatedAssetsPath = path.join(tmpDir, 'Assets.car'); + if (!fs.existsSync(generatedAssetsPath)) { + throw new Error(`actool did not generate Assets.car at ${generatedAssetsPath}`); + } + + fs.copyFileSync(generatedAssetsPath, outputAssetsPath); + console.log(`Generated ${outputAssetsPath}`); +} finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); +}