fix(desktop): let macOS system quit bypass background close (#3203)

Let macOS system-level Quit (Dock Quit, app menu Quit, Cmd-Q) bypass the close-to-background preference. Swizzles AppDelegate applicationShouldTerminate: and WailsContext Quit to mark a system-quit flag that beforeClose consumes; ordinary window close behavior unchanged and covered by a regression test.
This commit is contained in:
SivanCola authored and GitHub committed 2026-06-05 00:36:27 -07:00
1 parent 0c020ba4c6
commit fea1d664f1
6 files changed
+110 -1

No files matched your search

+2 -1
View File
@@ -89,12 +89,13 @@ func (a *App) Platform() string {
// off the initialization in a background goroutine so the webview loads immediately.
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
installSystemQuitHook()
go a.restoreOrBuildTabs()
}
func (a *App) beforeClose(ctx context.Context) bool {
if a.forceQuit.Swap(false) {
if a.forceQuit.Swap(false) || consumeSystemQuitRequested() {
return false
}
cfg, _, err := a.loadDesktopUserConfigForEdit()
+22
View File
@@ -73,6 +73,28 @@ func TestEffortDefaultsBeforeStartup(t *testing.T) {
}
}
func TestBeforeCloseAllowsSystemQuitWhenBackgroundCloseEnabled(t *testing.T) {
isolateDesktopUserDirs(t)
consumeSystemQuitRequested()
t.Cleanup(func() { consumeSystemQuitRequested() })
userCfg := config.LoadForEdit(config.UserConfigPath())
if err := userCfg.SetDesktopCloseBehavior("background"); err != nil {
t.Fatal(err)
}
if err := userCfg.SaveTo(config.UserConfigPath()); err != nil {
t.Fatal(err)
}
markSystemQuitRequested()
if prevent := NewApp().beforeClose(context.Background()); prevent {
t.Fatal("system quit should bypass background close-to-tray behavior")
}
if consumeSystemQuitRequested() {
t.Fatal("system quit marker should be consumed by beforeClose")
}
}
func TestEmitReadyInvokesReadyHook(t *testing.T) {
app := NewApp()
var calls int32
+13
View File
@@ -0,0 +1,13 @@
package main
import "sync/atomic"
var systemQuitRequested atomic.Bool
func markSystemQuitRequested() {
systemQuitRequested.Store(true)
}
func consumeSystemQuitRequested() bool {
return systemQuitRequested.Swap(false)
}
+24
View File
@@ -0,0 +1,24 @@
//go:build darwin
package main
/*
#cgo darwin LDFLAGS: -framework Cocoa
void installReasonixSystemQuitHook(void);
*/
import "C"
import "sync"
var installSystemQuitHookOnce sync.Once
func installSystemQuitHook() {
installSystemQuitHookOnce.Do(func() {
C.installReasonixSystemQuitHook()
})
}
//export ReasonixMarkSystemQuit
func ReasonixMarkSystemQuit() {
markSystemQuitRequested()
}
+44
View File
@@ -0,0 +1,44 @@
//go:build darwin
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
extern void ReasonixMarkSystemQuit(void);
static NSApplicationTerminateReply (*originalApplicationShouldTerminate)(id, SEL, NSApplication *);
static void (*originalWailsContextQuit)(id, SEL);
static NSApplicationTerminateReply reasonixApplicationShouldTerminate(id self, SEL _cmd, NSApplication *sender) {
ReasonixMarkSystemQuit();
if (originalApplicationShouldTerminate != NULL) {
return originalApplicationShouldTerminate(self, _cmd, sender);
}
return NSTerminateNow;
}
static void reasonixWailsContextQuit(id self, SEL _cmd) {
ReasonixMarkSystemQuit();
if (originalWailsContextQuit != NULL) {
originalWailsContextQuit(self, _cmd);
}
}
void installReasonixSystemQuitHook(void) {
Class appDelegate = NSClassFromString(@"AppDelegate");
SEL selector = @selector(applicationShouldTerminate:);
Method method = appDelegate == Nil ? NULL : class_getInstanceMethod(appDelegate, selector);
if (method != NULL) {
IMP replacement = (IMP)reasonixApplicationShouldTerminate;
IMP previous = method_setImplementation(method, replacement);
originalApplicationShouldTerminate = (NSApplicationTerminateReply (*)(id, SEL, NSApplication *))previous;
}
Class wailsContext = NSClassFromString(@"WailsContext");
SEL quitSelector = @selector(Quit);
Method quitMethod = wailsContext == Nil ? NULL : class_getInstanceMethod(wailsContext, quitSelector);
if (quitMethod != NULL) {
IMP replacement = (IMP)reasonixWailsContextQuit;
IMP previous = method_setImplementation(quitMethod, replacement);
originalWailsContextQuit = (void (*)(id, SEL))previous;
}
}
+5
View File
@@ -0,0 +1,5 @@
//go:build !darwin
package main
func installSystemQuitHook() {}