diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index b3c1761..237e66c 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -3,8 +3,16 @@ import FlutterMacOS @main class AppDelegate: FlutterAppDelegate { + /// 关窗不退出(托盘常驻的核心开关)。 + /// + /// 红点按钮的真实 action 是 AppKit 私有 `_close:`(实测 macOS 15,并非文档所说 + /// 的 performClose:),会绕过 performClose/close 的重写把窗口真正关闭;窗口关闭后 + /// AppKit 询问本方法——Flutter 模板默认返回 true(关窗即退出),这正是"点红点 + /// 直接退出、托盘消失"的根因。返回 false 后:窗口关闭时进程/HTTP 服务/托盘保持 + /// 运行,托盘点击 makeKeyAndOrderFront 恢复窗口(xib releasedWhenClosed=NO, + /// 窗口对象未释放)。真正退出只走托盘菜单 → NSApp.terminate / Cmd+Q。 override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true + return false } override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 2af11c4..9b4728b 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -14,13 +14,18 @@ class MainFlutterWindow: NSWindow { super.awakeFromNib() } - /// 关窗不退出(双保险):红点按钮与 Cmd+W 的 target 都是 performClose:, - /// 这里直接 orderOut 隐藏窗口,进程/服务/托盘保持运行; - /// 真正退出只走托盘菜单 → window_manager.destroy() → NSApp.terminate(不经过此处)。 - /// window_manager 的 setPreventClose/delegate 拦截在 xib 窗口上挂载时机不可靠, - /// 不能作为关窗拦截的唯一依赖(实测会被绕过导致整窗关闭)。 + /// 关窗不退出(最终拦截点)。实测(macOS 15)红点按钮的真实 action 是 AppKit + /// 私有 selector `_close:`(并非文档所称的 performClose:),其内部直接调 close(), + /// 会绕过 performClose/windowShouldClose/delegate 拦截——因此必须在 close() 上拦。 + /// 此处 orderOut 隐藏窗口,进程/HTTP 服务/托盘保持运行;NSApp.terminate 流程中 + /// 的窗口关闭不受影响(进程照常结束),真正退出只走托盘菜单。 + override func close() { + print("MainFlutterWindow: close() 拦截,窗口隐藏到托盘(服务继续运行)") + orderOut(nil) + } + + /// Cmd+W / 菜单"关闭窗口"走 performClose:,同样只隐藏不关闭。 override func performClose(_ sender: Any?) { - print("MainFlutterWindow: performClose 拦截,窗口隐藏到托盘(服务继续运行)") orderOut(sender) } }