mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 00:46:45 +00:00
fix(electron): prevent app hang on macOS native quit (Cmd+Q, Dock > Quit)
On macOS, native quit fires before-quit BEFORE window close events. The before-quit handler was calling ipcMain.removeAllListeners(), which destroyed the BEFORE_CLOSE_DONE IPC listener. The close handler then sent NOTIFY_ON_CLOSE to the renderer (for sync/finish-day callbacks), the renderer responded with BEFORE_CLOSE_DONE, but the listener was gone — causing a permanent hang. Fix: intercept before-quit when isQuiting=false and delegate to win.close(), which re-enters the existing close handler and runs before-close callbacks normally. Move ipcMain.removeAllListeners() to will-quit, which fires after all windows are closed and all IPC flows are guaranteed complete. Also improves close event log to include pending before-close handler IDs.
This commit is contained in:
parent
a4fe03272a
commit
5ce78a5b63
2 changed files with 26 additions and 12 deletions
|
|
@ -455,7 +455,7 @@ const appCloseHandler = (app: App): void => {
|
|||
|
||||
mainWin.on('close', (event) => {
|
||||
// NOTE: this might not work if we run a second instance of the app
|
||||
log('close, isQuiting:', getIsQuiting());
|
||||
log('close event: isQuiting=', getIsQuiting(), 'pendingBeforeCloseIds=', ids);
|
||||
if (!getIsQuiting()) {
|
||||
event.preventDefault();
|
||||
if (getIsMinimizeToTray()) {
|
||||
|
|
|
|||
|
|
@ -21,14 +21,14 @@ import { CONFIG } from './CONFIG';
|
|||
import { lazySetInterval } from './shared-with-frontend/lazy-set-interval';
|
||||
import { initIndicator } from './indicator';
|
||||
import { quitApp, showOrFocus } from './various-shared';
|
||||
import { createWindow } from './main-window';
|
||||
import { createWindow, getWin } from './main-window';
|
||||
import { IdleTimeHandler } from './idle-time-handler';
|
||||
import { destroyTaskWidget } from './task-widget/task-widget';
|
||||
import {
|
||||
initializeProtocolHandling,
|
||||
processPendingProtocolUrls,
|
||||
} from './protocol-handler';
|
||||
import { getIsQuiting, setIsLocked } from './shared-state';
|
||||
import { getIsQuiting, setIsQuiting, setIsLocked } from './shared-state';
|
||||
|
||||
const ICONS_FOLDER = __dirname + '/assets/icons/';
|
||||
const IS_MAC = process.platform === 'darwin';
|
||||
|
|
@ -299,18 +299,32 @@ export const startApp = (): void => {
|
|||
appIN.on('will-quit', () => {
|
||||
// un-register all shortcuts.
|
||||
globalShortcut.unregisterAll();
|
||||
// Safe to remove IPC listeners here: all windows are closed and before-close
|
||||
// IPC flows (sync, finish-day) are guaranteed to have completed.
|
||||
ipcMain.removeAllListeners();
|
||||
});
|
||||
|
||||
appIN.on('before-quit', () => {
|
||||
log('App before-quit: cleaning up resources');
|
||||
|
||||
// Clean up task widget before quitting
|
||||
appIN.on('before-quit', (event) => {
|
||||
log('App before-quit: isQuiting=', getIsQuiting());
|
||||
if (!getIsQuiting()) {
|
||||
// Native quit path (Cmd+Q, Dock > Quit on macOS): app.quit() was called
|
||||
// by the OS without going through quitApp(), so isQuiting was never set.
|
||||
// Prevent the immediate quit and delegate to the window close handler,
|
||||
// which manages the before-close callback flow (sync, finish-day, etc.)
|
||||
// and sets isQuiting=true before re-quitting.
|
||||
event.preventDefault();
|
||||
const win = getWin();
|
||||
if (win && !win.isDestroyed()) {
|
||||
win.close();
|
||||
} else {
|
||||
// No window to close — set flag and re-trigger quit directly.
|
||||
setIsQuiting(true);
|
||||
app.quit();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// isQuiting=true: all before-close IPC work is complete — safe to clean up.
|
||||
destroyTaskWidget();
|
||||
|
||||
// Remove all IPC listeners to prevent memory leaks
|
||||
ipcMain.removeAllListeners();
|
||||
|
||||
// Clear any pending timeouts/intervals
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue