feat: add native macOS dynamic app icon (#1402)
* feat: add native macOS dynamic app icon Adds Icon Composer source and compiled Assets.car for macOS Packages the dynamic icon asset into the Electron app Adds a script to regenerate macOS icon assets * fix: harden macOS icon asset scripts
This commit is contained in:
committed by
GitHub
parent
0c8301d579
commit
3f3b835ee9
@@ -27,6 +27,7 @@
|
|||||||
"build:web-assets": "node ./scripts/build-web-assets.mjs",
|
"build:web-assets": "node ./scripts/build-web-assets.mjs",
|
||||||
"build": "bun -e \"process.exit(0)\"",
|
"build": "bun -e \"process.exit(0)\"",
|
||||||
"bundle:main": "bun ./scripts/bundle-main.mjs",
|
"bundle:main": "bun ./scripts/bundle-main.mjs",
|
||||||
|
"generate:macos-icon": "node ./scripts/generate-macos-icon-assets.cjs",
|
||||||
"rebuild:native": "node ./scripts/rebuild-native.mjs",
|
"rebuild:native": "node ./scripts/rebuild-native.mjs",
|
||||||
"package": "bun run build:web-assets && bun run bundle:main && bun run rebuild:native && electron-builder",
|
"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",
|
"finalize:latest-yml": "node ./scripts/finalize-latest-yml.mjs",
|
||||||
@@ -46,6 +47,7 @@
|
|||||||
"to": "web-dist"
|
"to": "web-dist"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"afterPack": "scripts/after-pack.cjs",
|
||||||
"directories": {
|
"directories": {
|
||||||
"buildResources": "resources/icons",
|
"buildResources": "resources/icons",
|
||||||
"output": "dist"
|
"output": "dist"
|
||||||
@@ -55,6 +57,9 @@
|
|||||||
"mac": {
|
"mac": {
|
||||||
"category": "public.app-category.developer-tools",
|
"category": "public.app-category.developer-tools",
|
||||||
"icon": "resources/icons/icon.icns",
|
"icon": "resources/icons/icon.icns",
|
||||||
|
"extendInfo": {
|
||||||
|
"CFBundleIconName": "AppIcon"
|
||||||
|
},
|
||||||
"hardenedRuntime": true,
|
"hardenedRuntime": true,
|
||||||
"gatekeeperAssess": false,
|
"gatekeeperAssess": false,
|
||||||
"entitlements": "resources/entitlements.mac.plist",
|
"entitlements": "resources/entitlements.mac.plist",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -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'));
|
||||||
|
};
|
||||||
@@ -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 });
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user