From 4d3956a441a97765b506e6bf803884e758912a91 Mon Sep 17 00:00:00 2001 From: 404ARE <936233544@qq.com> Date: Sat, 22 Aug 2026 02:53:44 +0800 Subject: [PATCH] fix(desktop): ship assets/ so the packaged window icon resolves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `files` in the builder config carries `dist/`, `dist-renderer/` and `package.json`. It does not carry `assets/`, but `createWindow` resolves the window icon out of it: icon: join(import.meta.dirname, '..', '..', 'assets', 'icon.png') In a packaged app that path does not exist. The failure is silent — Electron reports an unreadable file as an empty NativeImage rather than raising — so the window simply carries no icon, which is most visible on Linux where there is no executable icon to fall back on. Copy `assets/` beside the app the way `resources/status` already is, and resolve the root against `process.resourcesPath` when packaged. The dev path is unchanged. --- apps/desktop/electron-builder.config.mjs | 8 ++++++ .../src/main/__tests__/desktop-assets.test.ts | 18 ++++++++++++ apps/desktop/src/main/desktop-assets.ts | 28 +++++++++++++++++++ apps/desktop/src/main/main-window.ts | 3 +- 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 apps/desktop/src/main/__tests__/desktop-assets.test.ts create mode 100644 apps/desktop/src/main/desktop-assets.ts diff --git a/apps/desktop/electron-builder.config.mjs b/apps/desktop/electron-builder.config.mjs index 24a451e100..03436ca5ff 100644 --- a/apps/desktop/electron-builder.config.mjs +++ b/apps/desktop/electron-builder.config.mjs @@ -59,6 +59,14 @@ export default { from: 'bundled-tools.json', to: 'bundled-tools.json', }, + { + // The app icon is read at runtime by the BrowserWindow `icon` option, and + // `files` above does not carry `assets/`. Electron reports the missing + // file as an empty image rather than an error, so without this the + // packaged app just draws no window icon. + from: 'assets', + to: 'assets', + }, { // Menu bar status item art. Without this the packaged app resolves an // empty NativeImage and Electron silently shows no icon at all. diff --git a/apps/desktop/src/main/__tests__/desktop-assets.test.ts b/apps/desktop/src/main/__tests__/desktop-assets.test.ts new file mode 100644 index 0000000000..ab34bd43ee --- /dev/null +++ b/apps/desktop/src/main/__tests__/desktop-assets.test.ts @@ -0,0 +1,18 @@ +import assert from 'node:assert/strict'; +import { join } from 'node:path'; +import { test } from 'node:test'; +import { desktopAssetPath, desktopAssetRoot } from '../desktop-assets.js'; + +test('a packaged build reads assets from the copy beside the app', () => { + const resourcesPath = join('/Applications', 'Maka.app', 'Contents', 'Resources'); + assert.equal(desktopAssetRoot({ isPackaged: true, resourcesPath }), resourcesPath); + assert.equal( + desktopAssetPath({ isPackaged: true, resourcesPath }, 'assets', 'icon.png'), + join(resourcesPath, 'assets', 'icon.png'), + ); +}); + +test('a dev run keeps resolving the repo layout, not the resources path', () => { + const root = desktopAssetRoot({ isPackaged: false, resourcesPath: '/unused' }); + assert.ok(root.endsWith(join('apps', 'desktop')), `${root} should point at apps/desktop`); +}); diff --git a/apps/desktop/src/main/desktop-assets.ts b/apps/desktop/src/main/desktop-assets.ts new file mode 100644 index 0000000000..e3466a1a75 --- /dev/null +++ b/apps/desktop/src/main/desktop-assets.ts @@ -0,0 +1,28 @@ +import { join } from 'node:path'; + +/** + * Root that `apps/desktop/assets` hangs off at runtime. + * + * Dev resolves the repo layout: two levels up from the built main bundle in + * `dist/main/` lands on `apps/desktop`. A packaged app has no such tree — + * `files` in the builder config carries `dist/`, `dist-renderer/` and + * `package.json`, and nothing else — so the assets ride along as an extra + * resource and the same segments hang off `process.resourcesPath` instead. + * + * Resolving the dev path in a packaged build fails silently: Electron reports + * an unreadable file as an EMPTY NativeImage rather than as an error, and the + * BrowserWindow `icon` option simply draws nothing. + */ +export function desktopAssetRoot(runtime: { + readonly isPackaged: boolean; + readonly resourcesPath: string; +}): string { + return runtime.isPackaged ? runtime.resourcesPath : join(import.meta.dirname, '..', '..'); +} + +export function desktopAssetPath( + runtime: { readonly isPackaged: boolean; readonly resourcesPath: string }, + ...segments: readonly string[] +): string { + return join(desktopAssetRoot(runtime), ...segments); +} diff --git a/apps/desktop/src/main/main-window.ts b/apps/desktop/src/main/main-window.ts index b84a229671..67703c5158 100644 --- a/apps/desktop/src/main/main-window.ts +++ b/apps/desktop/src/main/main-window.ts @@ -3,6 +3,7 @@ import { mkdir } from 'node:fs/promises'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; import type { AppSettings } from '@maka/core/settings'; +import { desktopAssetPath } from './desktop-assets.js'; import { isExternalUrl } from './external-link-guard.js'; import { readSavedBounds, writeSavedBounds, SAFE_MIN_HEIGHT, SAFE_MIN_WIDTH, type SavedBounds } from './window-state.js'; import { BrowserViewController } from './browser/controller.js'; @@ -268,7 +269,7 @@ export function createMainWindowController(deps: MainWindowControllerDeps): Main // / window title bar; .icns / .ico packaging will come with the // installer build pass. The asset path resolves from the built // dist/main/main.js (two levels up to apps/desktop, then assets). - icon: join(import.meta.dirname, '..', '..', 'assets', 'icon.png'), + icon: desktopAssetPath({ isPackaged: app.isPackaged, resourcesPath: process.resourcesPath }, 'assets', 'icon.png'), // PR-WINDOW-TITLEBAR-0: hide the native title bar so the renderer // chrome can extend to the top edge on every platform. macOS keeps // `hiddenInset` + traffic-light buttons (top-left); Windows uses