2037144a5c
Release / release (push) Successful in 5m8s
nativeImage from paths inside app.asar often yields empty images on Windows. Ship icon.ico via extraResources and read with createFromBuffer; setIcon on boot + app windows. Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
3.1 KiB
TypeScript
115 lines
3.1 KiB
TypeScript
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
|
||
import { app, nativeImage } from 'electron';
|
||
|
||
let resolved = false;
|
||
let cached: Electron.NativeImage | undefined;
|
||
|
||
/** ICO рядом с exe (вне asar): надёжно для `nativeImage` / панели задач на Windows. */
|
||
function getPackagedBrandingIcoPath(): string | undefined {
|
||
if (!app.isPackaged) return undefined;
|
||
const p = path.join(process.resourcesPath, 'branding', 'icon.ico');
|
||
try {
|
||
if (fs.existsSync(p)) return p;
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
function brandingPngPaths(): string[] {
|
||
const root = app.getAppPath();
|
||
const relPack = path.join('dist', 'renderer', 'app-pack-icon.png');
|
||
const relWindow = path.join('dist', 'renderer', 'app-window-icon.png');
|
||
const paths: string[] = [];
|
||
if (app.isPackaged) {
|
||
const unpacked = path.join(process.resourcesPath, 'app.asar.unpacked');
|
||
paths.push(path.join(unpacked, relPack), path.join(unpacked, relWindow));
|
||
}
|
||
paths.push(
|
||
path.join(root, relPack),
|
||
path.join(root, relWindow),
|
||
path.join(root, 'build', 'icon.png'),
|
||
path.join(root, 'app', 'renderer', 'public', 'app-window-icon.png'),
|
||
);
|
||
return paths;
|
||
}
|
||
|
||
function tryLoadImageFile(filePath: string): Electron.NativeImage | undefined {
|
||
try {
|
||
const buf = fs.readFileSync(filePath);
|
||
const fromBuf = nativeImage.createFromBuffer(buf);
|
||
if (!fromBuf.isEmpty()) return fromBuf;
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
try {
|
||
const fromPath = nativeImage.createFromPath(filePath);
|
||
if (!fromPath.isEmpty()) return fromPath;
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
function tryLoadSvgFile(filePath: string): Electron.NativeImage | undefined {
|
||
try {
|
||
const fromPath = nativeImage.createFromPath(filePath);
|
||
if (!fromPath.isEmpty()) return fromPath;
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
function tryDarwinSvgPaths(): Electron.NativeImage | undefined {
|
||
if (process.platform !== 'darwin') return undefined;
|
||
const root = app.getAppPath();
|
||
for (const p of [
|
||
path.join(root, 'dist', 'renderer', 'app-logo.svg'),
|
||
path.join(root, 'app', 'renderer', 'public', 'app-logo.svg'),
|
||
]) {
|
||
if (!fs.existsSync(p)) continue;
|
||
const img = tryLoadSvgFile(p);
|
||
if (img) return img;
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
/**
|
||
* Иконка окна / дока. Сначала ICO из `extraResources` (реальный путь на диске), затем PNG
|
||
* через буфер — `createFromPath` к файлам внутри `app.asar` на Windows часто даёт пустой `NativeImage`.
|
||
*/
|
||
export function loadBrandingWindowIcon(): Electron.NativeImage | undefined {
|
||
if (resolved) return cached;
|
||
resolved = true;
|
||
|
||
const ico = getPackagedBrandingIcoPath();
|
||
if (ico) {
|
||
const img = tryLoadImageFile(ico);
|
||
if (img) {
|
||
cached = img;
|
||
return cached;
|
||
}
|
||
}
|
||
|
||
for (const p of brandingPngPaths()) {
|
||
if (!fs.existsSync(p)) continue;
|
||
const img = tryLoadImageFile(p);
|
||
if (img) {
|
||
cached = img;
|
||
return cached;
|
||
}
|
||
}
|
||
|
||
const svgIcon = tryDarwinSvgPaths();
|
||
if (svgIcon) {
|
||
cached = svgIcon;
|
||
return cached;
|
||
}
|
||
|
||
cached = undefined;
|
||
return undefined;
|
||
}
|