From d43e0c126b724ab777092a570617441a6ab69ced Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Mon, 30 Mar 2026 17:57:20 -0700 Subject: [PATCH] Fix macOS quit: intercept applicationShouldTerminate: to allow real exit GLFW's applicationShouldTerminate: converts all quit paths (Cmd+Q, dock, menu bar) into window close requests and returns NSTerminateCancel, so our close-to-hide intercept was hiding the window instead of letting the app exit. Fix by implementing applicationShouldTerminate: in ReopenDelegate, setting a darwinQuitting flag before forwarding to GLFW's delegate. The close intercept checks this flag and calls Window.Close() instead of Hide() when quitting. Co-Authored-By: Claude Sonnet 4.6 --- ui/app_delegate_darwin.go | 13 +++++++++++++ ui/app_delegate_darwin.m | 11 +++++++++++ ui/app_delegate_other.go | 4 ++++ ui/mainwindow.go | 2 +- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/ui/app_delegate_darwin.go b/ui/app_delegate_darwin.go index 0138fc4..7b430a8 100644 --- a/ui/app_delegate_darwin.go +++ b/ui/app_delegate_darwin.go @@ -11,6 +11,14 @@ import "fyne.io/fyne/v2" var darwinAppDelegateReopenWindow fyne.Window +// darwinQuitting is set to true when applicationShouldTerminate: fires so +// the close intercept knows to actually close instead of hide. +var darwinQuitting bool + +func isRealQuit() bool { + return darwinQuitting +} + func installReopenHandler(w fyne.Window) { darwinAppDelegateReopenWindow = w C.installReopenDelegate() @@ -25,3 +33,8 @@ func appReopened() { fyne.Do(darwinAppDelegateReopenWindow.Show) }() } + +//export appShouldTerminate +func appShouldTerminate() { + darwinQuitting = true +} diff --git a/ui/app_delegate_darwin.m b/ui/app_delegate_darwin.m index 063cdaf..67621ce 100644 --- a/ui/app_delegate_darwin.m +++ b/ui/app_delegate_darwin.m @@ -3,6 +3,7 @@ #import extern void appReopened(void); +extern void appShouldTerminate(void); @interface ReopenDelegate : NSObject @property (nonatomic, strong) id wrapped; @@ -11,6 +12,16 @@ extern void appReopened(void); @implementation ReopenDelegate +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { + // Set the quitting flag before GLFW's delegate fires close requests for + // each window, so our close intercept knows to close rather than hide. + appShouldTerminate(); + if ([self.wrapped respondsToSelector:_cmd]) { + return [self.wrapped applicationShouldTerminate:sender]; + } + return NSTerminateNow; +} + - (BOOL)applicationShouldHandleReopen:(NSApplication *)app hasVisibleWindows:(BOOL)hasVisible { if (!hasVisible) { diff --git a/ui/app_delegate_other.go b/ui/app_delegate_other.go index fa39367..c48ba75 100644 --- a/ui/app_delegate_other.go +++ b/ui/app_delegate_other.go @@ -6,3 +6,7 @@ import "fyne.io/fyne/v2" func installReopenHandler(w fyne.Window) { } + +func isRealQuit() bool { + return false +} diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 6393eb2..61581d0 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -185,7 +185,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string, m.Window.SetCloseIntercept(func() { m.SaveWindowSettings() - if runtime.GOOS == "darwin" || (app.Config.Application.CloseToSystemTray && m.HaveSystemTray()) { + if (runtime.GOOS == "darwin" && !isRealQuit()) || (app.Config.Application.CloseToSystemTray && m.HaveSystemTray()) { m.Window.Hide() } else { m.Window.Close()