fix(desktop): tray badge — IPC handler died when icon.ico missing from packaged resources
This commit is contained in:
@@ -30,6 +30,17 @@ function resolveIconPathDev(): string {
|
|||||||
return path.join(app.getAppPath(), 'resources', 'icon.ico');
|
return path.join(app.getAppPath(), 'resources', 'icon.ico');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 16×16 grey square — last-ditch fallback when neither the packaged
|
||||||
|
// nor dev icon file resolves. Tray constructor on Windows throws when
|
||||||
|
// handed an empty NativeImage, which would tear down the whole
|
||||||
|
// registrar before `ipcMain.handle(TRAY_UNREAD)` runs — leaving the
|
||||||
|
// taskbar overlay badge wired but the renderer's invoke rejecting
|
||||||
|
// with "No handler registered". A non-empty placeholder keeps the
|
||||||
|
// constructor happy so the IPC handler always gets registered.
|
||||||
|
const FALLBACK_TRAY_PNG_BASE64 =
|
||||||
|
'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAH0lEQVR42mNk' +
|
||||||
|
'YGD4z0ABYBxVOKpwVOGowlGFwwoBAEnYAR9XlIldAAAAAElFTkSuQmCC';
|
||||||
|
|
||||||
function loadTrayIcon(): NativeImage {
|
function loadTrayIcon(): NativeImage {
|
||||||
for (const p of [resolveIconPath(), resolveIconPathDev()]) {
|
for (const p of [resolveIconPath(), resolveIconPathDev()]) {
|
||||||
try {
|
try {
|
||||||
@@ -39,7 +50,7 @@ function loadTrayIcon(): NativeImage {
|
|||||||
/* try next */
|
/* try next */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nativeImage.createEmpty();
|
return nativeImage.createFromBuffer(Buffer.from(FALLBACK_TRAY_PNG_BASE64, 'base64'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildOverlay(): NativeImage {
|
function buildOverlay(): NativeImage {
|
||||||
@@ -53,35 +64,49 @@ function buildOverlay(): NativeImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function register(mainWindow: BrowserWindow): void {
|
export function register(mainWindow: BrowserWindow): void {
|
||||||
const icon = loadTrayIcon();
|
// Tray icon is best-effort: if neither the packaged resource nor the
|
||||||
trayRef = new Tray(icon.isEmpty() ? nativeImage.createEmpty() : icon);
|
// dev path resolves (e.g. icon.ico isn't shipped under resourcesPath
|
||||||
trayRef.setToolTip('Netralax');
|
// in packaged builds — only the app icon goes into the .exe metadata),
|
||||||
|
// we still want the taskbar overlay badge to work. setOverlayIcon is
|
||||||
|
// a BrowserWindow method, so it functions even when the systray icon
|
||||||
|
// creation fails.
|
||||||
|
try {
|
||||||
|
const icon = loadTrayIcon();
|
||||||
|
if (!icon.isEmpty()) {
|
||||||
|
trayRef = new Tray(icon);
|
||||||
|
trayRef.setToolTip('Netralax');
|
||||||
|
|
||||||
const menu = Menu.buildFromTemplate([
|
const menu = Menu.buildFromTemplate([
|
||||||
{
|
{
|
||||||
label: 'Open',
|
label: 'Open',
|
||||||
click: (): void => {
|
click: (): void => {
|
||||||
|
if (mainWindow.isDestroyed()) return;
|
||||||
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||||
|
mainWindow.show();
|
||||||
|
mainWindow.focus();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
click: (): void => {
|
||||||
|
app.quit();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
trayRef.setContextMenu(menu);
|
||||||
|
trayRef.on('click', (): void => {
|
||||||
if (mainWindow.isDestroyed()) return;
|
if (mainWindow.isDestroyed()) return;
|
||||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||||
mainWindow.show();
|
if (mainWindow.isVisible()) mainWindow.focus();
|
||||||
mainWindow.focus();
|
else mainWindow.show();
|
||||||
},
|
});
|
||||||
},
|
}
|
||||||
{ type: 'separator' },
|
} catch (err: unknown) {
|
||||||
{
|
// Swallow: the overlay badge below is the user-visible bit. A missing
|
||||||
label: 'Quit',
|
// systray icon is cosmetic and shouldn't take the unread handler with it.
|
||||||
click: (): void => {
|
console.warn('[tray] systray init failed, overlay badge still active', err);
|
||||||
app.quit();
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
trayRef.setContextMenu(menu);
|
|
||||||
trayRef.on('click', (): void => {
|
|
||||||
if (mainWindow.isDestroyed()) return;
|
|
||||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
||||||
if (mainWindow.isVisible()) mainWindow.focus();
|
|
||||||
else mainWindow.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
CHANNELS.TRAY_UNREAD,
|
CHANNELS.TRAY_UNREAD,
|
||||||
|
|||||||
Reference in New Issue
Block a user