From 0bc325addc2720de4fdad637b34b20d780f681a4 Mon Sep 17 00:00:00 2001 From: Luiz Sardinha Date: Sun, 26 Jul 2026 13:25:14 +0200 Subject: [PATCH] fix(mac): properly handle system initiated quit events --- src/lib/gui/MainWindow.cpp | 30 +++++++++++++++++------- src/lib/gui/MainWindow.h | 1 + src/lib/gui/OSXHelpers.h | 3 +++ src/lib/gui/OSXHelpers.mm | 48 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/lib/gui/MainWindow.cpp b/src/lib/gui/MainWindow.cpp index 759b427ca..fbe64780e 100644 --- a/src/lib/gui/MainWindow.cpp +++ b/src/lib/gui/MainWindow.cpp @@ -163,6 +163,11 @@ MainWindow::MainWindow() applyConfig(); m_statusBar->setSecurityIcon(TlsUtility::isEnabled()); restoreWindow(); + +#ifdef Q_OS_MACOS + // Route native quits (Cmd+Q / Apple menu Quit / Dock "Quit") to the usual close-to-tray decision instead. + installQuitHandler([this] { return !maybeHideToTray(); }); +#endif } MainWindow::~MainWindow() { @@ -898,16 +903,25 @@ void MainWindow::handlePeerFingerprint(const QString &fingerprint) } } +bool MainWindow::maybeHideToTray() +{ + if (!Settings::value(Settings::Gui::CloseToTray).toBool()) { + return false; + } + + if (Settings::value(Settings::Gui::CloseReminder).toBool()) { + messages::showCloseReminder(this); + Settings::setValue(Settings::Gui::CloseReminder, false); + } + Settings::setValue(Settings::Gui::WindowGeometry, geometry()); + qDebug() << "hiding to tray"; + hide(); + return true; +} + void MainWindow::closeEvent(QCloseEvent *event) { - if (Settings::value(Settings::Gui::CloseToTray).toBool() && event->spontaneous()) { - if (Settings::value(Settings::Gui::CloseReminder).toBool()) { - messages::showCloseReminder(this); - Settings::setValue(Settings::Gui::CloseReminder, false); - } - Settings::setValue(Settings::Gui::WindowGeometry, geometry()); - qDebug() << "hiding to tray"; - hide(); + if (event->spontaneous() && maybeHideToTray()) { event->ignore(); return; } diff --git a/src/lib/gui/MainWindow.h b/src/lib/gui/MainWindow.h index 637e1fdcb..506fd35a1 100644 --- a/src/lib/gui/MainWindow.h +++ b/src/lib/gui/MainWindow.h @@ -122,6 +122,7 @@ private: void handlePeerFingerprint(const QString &fingerprint); void handleMissingKeyboardLayouts(const QString &layouts); void closeEvent(QCloseEvent *event) override; + bool maybeHideToTray(); void secureSocket(bool secureSocket); void connectSlots(); void handleLogLine(const QString &line); diff --git a/src/lib/gui/OSXHelpers.h b/src/lib/gui/OSXHelpers.h index bd09ca6a3..9cc119ff9 100644 --- a/src/lib/gui/OSXHelpers.h +++ b/src/lib/gui/OSXHelpers.h @@ -8,9 +8,12 @@ #include +#include + void requestOSXNotificationPermission(); bool isOSXDevelopmentBuild(); bool showOSXNotification(const QString &title, const QString &body); bool isOSXInterfaceStyleDark(); void forceAppActive(); void macOSNativeHide(); +void installQuitHandler(std::function shouldQuit); diff --git a/src/lib/gui/OSXHelpers.mm b/src/lib/gui/OSXHelpers.mm index 97e0fde25..6c523c681 100644 --- a/src/lib/gui/OSXHelpers.mm +++ b/src/lib/gui/OSXHelpers.mm @@ -13,12 +13,19 @@ #import #import #import +#import #import #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" +namespace { +std::function s_shouldQuit; +IMP s_originalShouldTerminate = nullptr; +BOOL s_isSystemShuttingDown = NO; +} // namespace + void requestOSXNotificationPermission() { #if OSX_DEPLOYMENT_TARGET >= 1014 @@ -107,3 +114,44 @@ void macOSNativeHide() [NSApp hide:nil]; [[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyAccessory]; } + +static NSApplicationTerminateReply deskflow_applicationShouldTerminate(id self, SEL _cmd, NSApplication *sender) +{ + // Don't intercept a system shutdown (or logoff/restart) + if (!s_isSystemShuttingDown && s_shouldQuit && !s_shouldQuit()) { + return NSTerminateCancel; + } + + // Execute Qt's applicationShouldTerminate + if (s_originalShouldTerminate) { + using ShouldTerminateFn = NSApplicationTerminateReply (*)(id, SEL, NSApplication *); + return reinterpret_cast(s_originalShouldTerminate)(self, _cmd, sender); + } + + return NSTerminateNow; +} + +void installQuitHandler(std::function shouldQuit) +{ + s_shouldQuit = std::move(shouldQuit); + + Class cls = [[NSApp delegate] class]; + SEL selector = @selector(applicationShouldTerminate:); + + Method method = class_getInstanceMethod(cls, selector); + if (method) { + s_originalShouldTerminate = method_getImplementation(method); + method_setImplementation(method, (IMP)deskflow_applicationShouldTerminate); + } else { + class_addMethod(cls, selector, (IMP)deskflow_applicationShouldTerminate, "l@:@"); + } + + // shutdown is also triggered for logout/restart + [[[NSWorkspace sharedWorkspace] notificationCenter] addObserverForName:NSWorkspaceWillPowerOffNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *note) { + Q_UNUSED(note) + s_isSystemShuttingDown = YES; + }]; +}