/** * System tray + minimize-to-tray (Phase O). When Settings.minimizeToTray is on, * closing the window hides it to the tray instead of quitting; the tray's "Quit" * really exits. The tray gives the app a background presence (a natural home for * the watched-source sync) and a quick show/quit menu. */ import { app, Tray, Menu, nativeImage, type BrowserWindow } from 'electron' import { getAppIconPath } from './binaries' // Fallback tray glyph (32×32 teal disc + white download arrow) used when no // build/icon.ico is present. Without this the tray is skipped (an empty image // makes an invisible/unclickable Windows tray entry), which would strand a // minimized-to-tray window with no way back. Embedded as base64 so it needs no // asset-bundling step. const FALLBACK_TRAY_PNG = 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAl0lEQVR4nO3TQQ6AIAxEUU7giV17bd0i' + 'aaDTmdqY0ISl/peirf11jvO6+/N5cHXKwlIIG6cQqngIoY5DiKy4C4G8aBwJohSArpIBmIgNKAWgDys' + 'AL8QGlANmCHZc8dUW1HEYEEFA6/d+B6q4CVAhwnHkb2DiUwCDkMRRBHpccQsRnXB8RLCAULxHMAAqbm0j5b4VoPRg1jzWxpzrS/JA7QAAAABJRU5ErkJggg==' let tray: Tray | null = null // True once the user has chosen to really quit (tray menu, or app.quit from the // updater) — lets the window 'close' handler tell "hide to tray" from "exit". let quitting = false export function isQuitting(): boolean { return quitting } export function markQuitting(): void { quitting = true } function show(getWindow: () => BrowserWindow | null): void { const win = getWindow() if (!win) return if (win.isMinimized()) win.restore() win.show() win.focus() } /** Create the tray icon + menu once. No-op if it already exists or the icon is missing. */ export function createTray(getWindow: () => BrowserWindow | null): void { if (tray) return // Prefer the real app icon; fall back to the embedded glyph when no icon.ico // ships, so minimize-to-tray always has a tray to restore from. let icon = nativeImage.createFromPath(getAppIconPath()) if (icon.isEmpty()) icon = nativeImage.createFromDataURL(`data:image/png;base64,${FALLBACK_TRAY_PNG}`) if (icon.isEmpty()) return tray = new Tray(icon) tray.setToolTip('AeroFetch') tray.setContextMenu( Menu.buildFromTemplate([ { label: 'Show AeroFetch', click: () => show(getWindow) }, { type: 'separator' }, { label: 'Quit AeroFetch', click: () => { quitting = true app.quit() } } ]) ) tray.on('click', () => show(getWindow)) }