From aa848d5ccf82b71728ea96a6a2d199524d20c5bd Mon Sep 17 00:00:00 2001 From: zhangmo8 Date: Wed, 3 Jun 2026 22:33:22 +0800 Subject: [PATCH 1/2] fix: macos active window service --- .../hooks/ready/eventListenerSetupHook.ts | 3 +++ src/main/presenter/windowPresenter/index.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/main/presenter/lifecyclePresenter/hooks/ready/eventListenerSetupHook.ts b/src/main/presenter/lifecyclePresenter/hooks/ready/eventListenerSetupHook.ts index 8a13972bb..1032b3966 100644 --- a/src/main/presenter/lifecyclePresenter/hooks/ready/eventListenerSetupHook.ts +++ b/src/main/presenter/lifecyclePresenter/hooks/ready/eventListenerSetupHook.ts @@ -45,6 +45,9 @@ export const eventListenerSetupHook: LifecycleHook = { if (!targetWindow.isDestroyed()) { targetWindow.show() targetWindow.focus() // Ensure window gets focus + // macOS: transparent windows may not properly activate the app, + // causing the global menu bar to not update. + app.focus() } else { console.warn( 'eventListenerSetupHook: App activated but target window is destroyed, creating new window.' diff --git a/src/main/presenter/windowPresenter/index.ts b/src/main/presenter/windowPresenter/index.ts index 4daa44f47..ba7715eab 100644 --- a/src/main/presenter/windowPresenter/index.ts +++ b/src/main/presenter/windowPresenter/index.ts @@ -1,5 +1,6 @@ // src\main\presenter\windowPresenter\index.ts import { + app, BrowserWindow, shell, nativeImage, @@ -357,6 +358,11 @@ export class WindowPresenter implements IWindowPresenter { targetWindow.show() if (shouldFocus) { targetWindow.focus() // Bring to foreground + // macOS: transparent windows may not properly activate the app, + // causing the global menu bar to not update. + if (process.platform === 'darwin') { + app.focus() + } } // 触发恢复逻辑以确保活动标签页可见且位置正确 this.handleWindowRestore(targetWindow.id).catch((error) => { @@ -699,6 +705,12 @@ export class WindowPresenter implements IWindowPresenter { if (!appWindow.isDestroyed()) { appWindow.show() appWindow.focus() + // macOS: transparent windows may not properly activate the app, + // causing the global menu bar to not update. Explicitly call app.focus() + // to ensure macOS recognizes DeepChat as the active application. + if (process.platform === 'darwin') { + app.focus() + } eventBus.sendToMain(WINDOW_EVENTS.WINDOW_CREATED, { windowId, isMainWindow: windowId === this.mainWindowId From 04634c60b0ed22d8fa8e0b7156e5661a78caaaf5 Mon Sep 17 00:00:00 2001 From: zhangmo8 Date: Wed, 3 Jun 2026 22:47:23 +0800 Subject: [PATCH 2/2] fix(mac): restore app foreground identity --- docs/issues/mac-app-name-identity/plan.md | 15 ++++++++++++++ docs/issues/mac-app-name-identity/spec.md | 20 +++++++++++++++++++ docs/issues/mac-app-name-identity/tasks.md | 6 ++++++ src/main/appMain.ts | 15 ++++++++++++++ src/main/lib/activateApp.ts | 19 ++++++++++++++++++ .../lifecyclePresenter/SplashWindowManager.ts | 2 ++ .../hooks/ready/eventListenerSetupHook.ts | 5 ++--- src/main/presenter/windowPresenter/index.ts | 18 ++++++----------- 8 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 docs/issues/mac-app-name-identity/plan.md create mode 100644 docs/issues/mac-app-name-identity/spec.md create mode 100644 docs/issues/mac-app-name-identity/tasks.md create mode 100644 src/main/lib/activateApp.ts diff --git a/docs/issues/mac-app-name-identity/plan.md b/docs/issues/mac-app-name-identity/plan.md new file mode 100644 index 000000000..e66c7884b --- /dev/null +++ b/docs/issues/mac-app-name-identity/plan.md @@ -0,0 +1,15 @@ +# macOS App Name Identity Plan + +## Implementation + +- Update the main-process startup path in `src/main/appMain.ts` to set the Electron application name to + `DeepChat` before the app creates windows or menus. +- Ensure the macOS process advertises itself as a regular foreground app and reveals its Dock identity + before startup windows attempt to take focus. +- Keep the change scoped to startup identity only, avoiding any unrelated menu, dock, or window policy + changes unless verification shows they are required. + +## Validation + +- Run a node-side typecheck or equivalent narrow validation for the touched startup file. +- Run repository-required format, i18n, and lint checks before handoff. diff --git a/docs/issues/mac-app-name-identity/spec.md b/docs/issues/mac-app-name-identity/spec.md new file mode 100644 index 000000000..76ae58751 --- /dev/null +++ b/docs/issues/mac-app-name-identity/spec.md @@ -0,0 +1,20 @@ +# macOS App Name Identity Spec + +## Goal + +On macOS, DeepChat should identify itself as the active application when its window is focused, +so the menu bar shows DeepChat instead of Finder or the generic Electron host identity. + +## Acceptance Criteria + +- DeepChat sets its user-visible application name during main-process startup before windows are created. +- DeepChat declares a regular foreground macOS activation policy before startup windows are shown. +- When a DeepChat window becomes the active foreground app on macOS, the menu bar app label resolves to + DeepChat rather than Finder. +- The change does not alter Windows or Linux startup behavior. + +## Non-Goals + +- No app icon, bundle identifier, or code-signing changes. +- No renderer UI changes. +- No shortcut or menu structure refactor. diff --git a/docs/issues/mac-app-name-identity/tasks.md b/docs/issues/mac-app-name-identity/tasks.md new file mode 100644 index 000000000..81b5300ff --- /dev/null +++ b/docs/issues/mac-app-name-identity/tasks.md @@ -0,0 +1,6 @@ +# macOS App Name Identity Tasks + +- [x] Document the bug, implementation scope, and validation plan. +- [x] Set the macOS-visible app name during main-process startup. +- [x] Run focused validation for the startup change. +- [x] Run required format, i18n, and lint checks. diff --git a/src/main/appMain.ts b/src/main/appMain.ts index d4041cb04..0879d3a27 100644 --- a/src/main/appMain.ts +++ b/src/main/appMain.ts @@ -13,8 +13,10 @@ import { storeStartupDeepLink } from './lib/startupDeepLink' import { isInsecureTlsAllowed } from './lib/insecureTls' +import { activateAppOnMac, ensureRegularAppOnMac } from './lib/activateApp' let appStarted = false +const APP_NAME = 'DeepChat' export function startApp(): void { if (appStarted) { @@ -22,6 +24,17 @@ export function startApp(): void { } appStarted = true + app.setName(APP_NAME) + if (process.platform === 'darwin') { + if (app.isReady()) { + ensureRegularAppOnMac() + } else { + app.once('ready', () => { + ensureRegularAppOnMac() + }) + } + } + registerWorkspacePreviewSchemes() // Handle unhandled exceptions to prevent app crash or error dialogs @@ -105,6 +118,7 @@ export function startApp(): void { } targetWindow.show() targetWindow.focus() + activateAppOnMac() } const routeIncomingDeeplink = (url: string, source: string) => { @@ -157,6 +171,7 @@ export function startApp(): void { // Start the lifecycle management system instead of using app.whenReady() app.whenReady().then(async () => { + ensureRegularAppOnMac() // Set app user model id for windows electronApp.setAppUserModelId('com.wefonk.deepchat') try { diff --git a/src/main/lib/activateApp.ts b/src/main/lib/activateApp.ts new file mode 100644 index 000000000..4d1725339 --- /dev/null +++ b/src/main/lib/activateApp.ts @@ -0,0 +1,19 @@ +import { app } from 'electron' + +export function ensureRegularAppOnMac(): void { + if (process.platform !== 'darwin') { + return + } + + app.setActivationPolicy('regular') + app.dock?.show() +} + +export function activateAppOnMac(): void { + if (process.platform !== 'darwin') { + return + } + + ensureRegularAppOnMac() + app.focus({ steal: true }) +} diff --git a/src/main/presenter/lifecyclePresenter/SplashWindowManager.ts b/src/main/presenter/lifecyclePresenter/SplashWindowManager.ts index d2f2b0add..bc6ccb0b9 100644 --- a/src/main/presenter/lifecyclePresenter/SplashWindowManager.ts +++ b/src/main/presenter/lifecyclePresenter/SplashWindowManager.ts @@ -27,6 +27,7 @@ import { type DatabaseUnlockRequestPayload, type DatabaseUnlockReason } from '@shared/contracts/databaseSecurity' +import { activateAppOnMac } from '@/lib/activateApp' type SplashActivityStatus = 'running' | 'completed' | 'failed' @@ -483,6 +484,7 @@ export class SplashWindowManager implements ISplashWindowManager { } this.splashWindow.show() this.splashWindow.focus() + activateAppOnMac() } private markSplashLoaded(): void { diff --git a/src/main/presenter/lifecyclePresenter/hooks/ready/eventListenerSetupHook.ts b/src/main/presenter/lifecyclePresenter/hooks/ready/eventListenerSetupHook.ts index 1032b3966..42360721c 100644 --- a/src/main/presenter/lifecyclePresenter/hooks/ready/eventListenerSetupHook.ts +++ b/src/main/presenter/lifecyclePresenter/hooks/ready/eventListenerSetupHook.ts @@ -11,6 +11,7 @@ import { WINDOW_EVENTS, TRAY_EVENTS, FLOATING_BUTTON_EVENTS, SETTINGS_EVENTS } f import { handleShowHiddenWindow } from '@/utils' import { presenter } from '@/presenter' import { LifecyclePhase } from '@shared/lifecycle' +import { activateAppOnMac } from '@/lib/activateApp' export const eventListenerSetupHook: LifecycleHook = { name: 'event-listener-setup', @@ -45,9 +46,7 @@ export const eventListenerSetupHook: LifecycleHook = { if (!targetWindow.isDestroyed()) { targetWindow.show() targetWindow.focus() // Ensure window gets focus - // macOS: transparent windows may not properly activate the app, - // causing the global menu bar to not update. - app.focus() + activateAppOnMac() } else { console.warn( 'eventListenerSetupHook: App activated but target window is destroyed, creating new window.' diff --git a/src/main/presenter/windowPresenter/index.ts b/src/main/presenter/windowPresenter/index.ts index ba7715eab..80ba530c4 100644 --- a/src/main/presenter/windowPresenter/index.ts +++ b/src/main/presenter/windowPresenter/index.ts @@ -1,6 +1,5 @@ // src\main\presenter\windowPresenter\index.ts import { - app, BrowserWindow, shell, nativeImage, @@ -36,6 +35,7 @@ import { FloatingChatWindow } from './FloatingChatWindow' // Floating chat windo import type { ProviderInstallPreview } from '@shared/providerDeeplink' import { StartupWorkloadCoordinator } from '../startupWorkloadCoordinator' import { openExternalUrl } from '@/lib/externalUrl' +import { activateAppOnMac } from '@/lib/activateApp' type PendingSettingsMessage = { channel: string @@ -358,11 +358,7 @@ export class WindowPresenter implements IWindowPresenter { targetWindow.show() if (shouldFocus) { targetWindow.focus() // Bring to foreground - // macOS: transparent windows may not properly activate the app, - // causing the global menu bar to not update. - if (process.platform === 'darwin') { - app.focus() - } + activateAppOnMac() } // 触发恢复逻辑以确保活动标签页可见且位置正确 this.handleWindowRestore(targetWindow.id).catch((error) => { @@ -535,6 +531,7 @@ export class WindowPresenter implements IWindowPresenter { if (switchToTarget) { targetWindow.show() targetWindow.focus() + activateAppOnMac() } return true @@ -705,12 +702,7 @@ export class WindowPresenter implements IWindowPresenter { if (!appWindow.isDestroyed()) { appWindow.show() appWindow.focus() - // macOS: transparent windows may not properly activate the app, - // causing the global menu bar to not update. Explicitly call app.focus() - // to ensure macOS recognizes DeepChat as the active application. - if (process.platform === 'darwin') { - app.focus() - } + activateAppOnMac() eventBus.sendToMain(WINDOW_EVENTS.WINDOW_CREATED, { windowId, isMainWindow: windowId === this.mainWindowId @@ -1243,6 +1235,7 @@ export class WindowPresenter implements IWindowPresenter { console.log('Settings window already exists, showing and focusing.') this.settingsWindow.show() this.settingsWindow.focus() + activateAppOnMac() if (navigation) { if (this.settingsWindowReady) { this.sendToWindow(this.settingsWindow.id, SETTINGS_EVENTS.NAVIGATE, navigation) @@ -1419,6 +1412,7 @@ export class WindowPresenter implements IWindowPresenter { mainWindow.show() mainWindow.focus() + activateAppOnMac() return true }