From 65ab6c89bf87aa57594d7686795105cf9d27ad8c Mon Sep 17 00:00:00 2001 From: pengx17 Date: Wed, 20 Mar 2024 11:02:22 +0000 Subject: [PATCH] fix(electron): optimize electron open/close on mac (#6224) 1. never close main window on mac to allow it to be quickly open 1. make the browser show a bit faster 2. brought up app window when clicking some menu items --- .../src/main/application-menu/create.ts | 13 +++- packages/frontend/electron/src/main/index.ts | 4 +- .../frontend/electron/src/main/main-window.ts | 66 ++++++++++++++----- .../frontend/electron/src/main/ui/handlers.ts | 4 +- .../src/main/windows-manager/launcher.ts | 4 +- 5 files changed, 67 insertions(+), 24 deletions(-) diff --git a/packages/frontend/electron/src/main/application-menu/create.ts b/packages/frontend/electron/src/main/application-menu/create.ts index bc3f39d0ef..c34816ded4 100644 --- a/packages/frontend/electron/src/main/application-menu/create.ts +++ b/packages/frontend/electron/src/main/application-menu/create.ts @@ -2,6 +2,7 @@ import { app, Menu } from 'electron'; import { isMacOS } from '../../shared/utils'; import { revealLogFile } from '../logger'; +import { initAndShowMainWindow } from '../main-window'; import { checkForUpdates } from '../updater'; import { applicationMenuSubjects } from './subject'; @@ -42,7 +43,9 @@ export function createApplicationMenu() { id: MENUITEM_NEW_PAGE, label: 'New Doc', accelerator: isMac ? 'Cmd+N' : 'Ctrl+N', - click: () => { + click: async () => { + await initAndShowMainWindow(); + // fixme: if the window is just created, the new page action will not be triggered applicationMenuSubjects.newPageAction.next(); }, }, @@ -100,7 +103,12 @@ export function createApplicationMenu() { { type: 'separator' }, { role: 'front' }, { type: 'separator' }, - { role: 'window' }, + { + role: 'window', + click: async () => { + await initAndShowMainWindow(); + }, + }, ] : [{ role: 'close' }]), ], @@ -125,6 +133,7 @@ export function createApplicationMenu() { { label: 'Check for Updates', click: async () => { + await initAndShowMainWindow(); await checkForUpdates(); }, }, diff --git a/packages/frontend/electron/src/main/index.ts b/packages/frontend/electron/src/main/index.ts index 43089abd5b..9aea79722a 100644 --- a/packages/frontend/electron/src/main/index.ts +++ b/packages/frontend/electron/src/main/index.ts @@ -52,9 +52,7 @@ if (!isSingleInstance) { * Shout down background process if all windows was closed */ app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit(); - } + app.quit(); }); /** diff --git a/packages/frontend/electron/src/main/main-window.ts b/packages/frontend/electron/src/main/main-window.ts index f8505d2a9a..add74ceb51 100644 --- a/packages/frontend/electron/src/main/main-window.ts +++ b/packages/frontend/electron/src/main/main-window.ts @@ -27,6 +27,14 @@ const getWindowAdditionalArguments = async () => { ]; }; +function closeAllWindows() { + BrowserWindow.getAllWindows().forEach(w => { + if (!w.isDestroyed()) { + w.destroy(); + } + }); +} + async function createWindow(additionalArguments: string[]) { logger.info('create window'); const mainWindowState = electronWindowState({ @@ -82,12 +90,7 @@ async function createWindow(additionalArguments: string[]) { * @see https://github.com/electron/electron/issues/25012 */ browserWindow.on('ready-to-show', () => { - if (IS_DEV) { - // do not gain focus in dev mode - browserWindow.showInactive(); - } else { - browserWindow.show(); - } + helperConnectionUnsub?.(); helperConnectionUnsub = helperProcessManager.connectRenderer( browserWindow.webContents ); @@ -96,15 +99,26 @@ async function createWindow(additionalArguments: string[]) { }); browserWindow.on('close', e => { - e.preventDefault(); - // close and destroy all windows - BrowserWindow.getAllWindows().forEach(w => { - if (!w.isDestroyed()) { - w.destroy(); - } - }); - helperConnectionUnsub?.(); // TODO: gracefully close the app, for example, ask user to save unsaved changes + e.preventDefault(); + if (!isMacOS()) { + closeAllWindows(); + } else { + // hide window on macOS + // application quit will be handled by closing the hidden window + // + // explanation: + // - closing the top window (by clicking close button or CMD-w) + // - will be captured in "close" event here + // - hiding the app to make the app open faster when user click the app icon + // - quit the app by "cmd+q" or right click on the dock icon and select "quit" + // - all browser windows will capture the "close" event + // - the hidden window will close all windows + // - "window-all-closed" event will be emitted and eventually quit the app + browserWindow.hide(); + } + helperConnectionUnsub?.(); + helperConnectionUnsub = undefined; }); browserWindow.on('leave-full-screen', () => { @@ -148,15 +162,37 @@ async function createWindow(additionalArguments: string[]) { // singleton let browserWindow$: Promise | undefined; +// a hidden window that prevents the app from quitting on MacOS +let hiddenMacWindow: BrowserWindow | undefined; + /** * Init main BrowserWindow. Will create a new window if it's not created yet. */ -export async function initMainWindow() { +export async function initAndShowMainWindow() { if (!browserWindow$ || (await browserWindow$.then(w => w.isDestroyed()))) { const additionalArguments = await getWindowAdditionalArguments(); browserWindow$ = createWindow(additionalArguments); } const mainWindow = await browserWindow$; + + if (IS_DEV) { + // do not gain focus in dev mode + mainWindow.showInactive(); + } else { + mainWindow.show(); + } + + if (!hiddenMacWindow && isMacOS()) { + hiddenMacWindow = new BrowserWindow({ + show: false, + width: 100, + height: 100, + }); + hiddenMacWindow.on('close', () => { + closeAllWindows(); + }); + } + return mainWindow; } diff --git a/packages/frontend/electron/src/main/ui/handlers.ts b/packages/frontend/electron/src/main/ui/handlers.ts index b43e40b29a..8650480cd1 100644 --- a/packages/frontend/electron/src/main/ui/handlers.ts +++ b/packages/frontend/electron/src/main/ui/handlers.ts @@ -4,7 +4,7 @@ import { getLinkPreview } from 'link-preview-js'; import { isMacOS } from '../../shared/utils'; import { persistentConfig } from '../config-storage/persist'; import { logger } from '../logger'; -import { getMainWindow, initMainWindow } from '../main-window'; +import { getMainWindow, initAndShowMainWindow } from '../main-window'; import { getOnboardingWindow } from '../onboarding'; import type { NamespaceHandlers } from '../type'; import { launchStage } from '../windows-manager/stage'; @@ -58,7 +58,7 @@ export const uiHandlers = { try { const onboarding = await getOnboardingWindow(); onboarding?.hide(); - await initMainWindow(); + await initAndShowMainWindow(); // need to destroy onboarding window after main window is ready // otherwise the main window will be closed as well onboarding?.destroy(); diff --git a/packages/frontend/electron/src/main/windows-manager/launcher.ts b/packages/frontend/electron/src/main/windows-manager/launcher.ts index f47b99c646..7c1cc2e5c5 100644 --- a/packages/frontend/electron/src/main/windows-manager/launcher.ts +++ b/packages/frontend/electron/src/main/windows-manager/launcher.ts @@ -1,5 +1,5 @@ import { logger } from '../logger'; -import { initMainWindow } from '../main-window'; +import { initAndShowMainWindow } from '../main-window'; import { getOnboardingWindow, getOrCreateOnboardingWindow, @@ -12,7 +12,7 @@ import { launchStage } from './stage'; export async function launch() { const stage = launchStage.value; if (stage === 'main') { - initMainWindow().catch(e => { + initAndShowMainWindow().catch(e => { logger.error('Failed to restore or create window:', e); });