From c440e95464bdca9ebbb2e2425e6436403b4a6ae0 Mon Sep 17 00:00:00 2001 From: Haze <709547807@qq.com> Date: Mon, 9 Feb 2026 22:20:51 +0800 Subject: [PATCH] chore(product): fix link in app (#22) --- electron-builder.yml | 5 +++- electron/gateway/manager.ts | 48 +++++++++++++++++++++++++++++++++-- electron/main/menu.ts | 2 +- resources/dmg-background.png | Bin 0 -> 5756 bytes src/pages/Settings/index.tsx | 2 +- 5 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 resources/dmg-background.png diff --git a/electron-builder.yml b/electron-builder.yml index 1a6ea96f4..d8760c6a6 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -62,9 +62,12 @@ mac: NSCameraUsageDescription: ClawX requires camera access for video features dmg: - # background: resources/dmg-background.png + background: resources/dmg-background.png icon: resources/icons/icon.icns iconSize: 100 + window: + width: 540 + height: 380 contents: - type: file x: 130 diff --git a/electron/gateway/manager.ts b/electron/gateway/manager.ts index 63209bb3f..3eeb1fb98 100644 --- a/electron/gateway/manager.ts +++ b/electron/gateway/manager.ts @@ -63,6 +63,39 @@ const DEFAULT_RECONNECT_CONFIG: ReconnectConfig = { maxDelay: 30000, }; +/** + * Get the Node.js-compatible executable path for spawning child processes. + * + * On macOS in packaged mode, using `process.execPath` directly causes the + * child process to appear as a separate dock icon (named "exec") because the + * binary lives inside a `.app` bundle that macOS treats as a GUI application. + * + * To avoid this, we resolve the Electron Helper binary which has + * `LSUIElement` set in its Info.plist, preventing dock icon creation. + * Falls back to `process.execPath` if the Helper binary is not found. + */ +function getNodeExecutablePath(): string { + if (process.platform === 'darwin' && app.isPackaged) { + // Electron Helper binary lives at: + // .app/Contents/Frameworks/ Helper.app/Contents/MacOS/ Helper + const appName = app.getName(); + const helperName = `${appName} Helper`; + const helperPath = path.join( + path.dirname(process.execPath), // .../Contents/MacOS + '../Frameworks', + `${helperName}.app`, + 'Contents/MacOS', + helperName, + ); + if (existsSync(helperPath)) { + logger.info(`Using Electron Helper binary to avoid dock icon: ${helperPath}`); + return helperPath; + } + logger.warn(`Electron Helper binary not found at ${helperPath}, falling back to process.execPath`); + } + return process.execPath; +} + /** * Gateway Manager * Handles starting, stopping, and communicating with the OpenClaw Gateway @@ -377,11 +410,13 @@ export class GatewayManager extends EventEmitter { const gatewayArgs = ['gateway', '--port', String(this.status.port), '--token', gatewayToken, '--dev', '--allow-unconfigured']; if (app.isPackaged) { - // Production: always use Electron binary as Node.js via ELECTRON_RUN_AS_NODE + // Production: use Electron binary as Node.js via ELECTRON_RUN_AS_NODE + // On macOS, use the Electron Helper binary to avoid extra dock icons if (existsSync(entryScript)) { - command = process.execPath; + command = getNodeExecutablePath(); args = [entryScript, ...gatewayArgs]; logger.info('Starting Gateway in PACKAGED mode (ELECTRON_RUN_AS_NODE)'); + logger.info(`Using executable: ${command}`); } else { const errMsg = `OpenClaw entry script not found at: ${entryScript}`; logger.error(errMsg); @@ -449,6 +484,15 @@ export class GatewayManager extends EventEmitter { // Critical: In packaged mode, make Electron binary act as Node.js if (app.isPackaged) { spawnEnv['ELECTRON_RUN_AS_NODE'] = '1'; + // Prevent OpenClaw entry.ts from respawning itself (which would create + // another child process and a second "exec" dock icon on macOS) + spawnEnv['OPENCLAW_NO_RESPAWN'] = '1'; + // Pre-set the NODE_OPTIONS that entry.ts would have added via respawn + const existingNodeOpts = spawnEnv['NODE_OPTIONS'] ?? ''; + if (!existingNodeOpts.includes('--disable-warning=ExperimentalWarning') && + !existingNodeOpts.includes('--no-warnings')) { + spawnEnv['NODE_OPTIONS'] = `${existingNodeOpts} --disable-warning=ExperimentalWarning`.trim(); + } } this.process = spawn(command, args, { diff --git a/electron/main/menu.ts b/electron/main/menu.ts index 8fbad60df..8aea57346 100644 --- a/electron/main/menu.ts +++ b/electron/main/menu.ts @@ -176,7 +176,7 @@ export function createMenu(): void { { label: 'Documentation', click: async () => { - await shell.openExternal('https://docs.clawx.app'); + await shell.openExternal('https://clawx.dev'); }, }, { diff --git a/resources/dmg-background.png b/resources/dmg-background.png new file mode 100644 index 0000000000000000000000000000000000000000..653a56357e1571eb414be3fcca7fec8d2235b6b4 GIT binary patch literal 5756 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 window.electron.openExternal('https://docs.clawx.app')} + onClick={() => window.electron.openExternal('https://clawx.dev')} > Documentation