fix(win): load window icon from ICO on disk + buffer PNG (1.0.6)
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>
This commit is contained in:
Ivan Fontosh
2026-05-12 08:24:14 +08:00
parent 1840227be6
commit 2037144a5c
6 changed files with 166 additions and 91 deletions
+114
View File
@@ -0,0 +1,114 @@
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;
}