fix(mac): properly handle system initiated quit events

This commit is contained in:
Luiz Sardinha 2026-07-26 13:25:14 +02:00 committed by Nick Bolton
parent 352f494bee
commit 0bc325addc
4 changed files with 74 additions and 8 deletions

View file

@ -163,6 +163,11 @@ MainWindow::MainWindow()
applyConfig(); applyConfig();
m_statusBar->setSecurityIcon(TlsUtility::isEnabled()); m_statusBar->setSecurityIcon(TlsUtility::isEnabled());
restoreWindow(); 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() MainWindow::~MainWindow()
{ {
@ -898,9 +903,12 @@ void MainWindow::handlePeerFingerprint(const QString &fingerprint)
} }
} }
void MainWindow::closeEvent(QCloseEvent *event) bool MainWindow::maybeHideToTray()
{ {
if (Settings::value(Settings::Gui::CloseToTray).toBool() && event->spontaneous()) { if (!Settings::value(Settings::Gui::CloseToTray).toBool()) {
return false;
}
if (Settings::value(Settings::Gui::CloseReminder).toBool()) { if (Settings::value(Settings::Gui::CloseReminder).toBool()) {
messages::showCloseReminder(this); messages::showCloseReminder(this);
Settings::setValue(Settings::Gui::CloseReminder, false); Settings::setValue(Settings::Gui::CloseReminder, false);
@ -908,6 +916,12 @@ void MainWindow::closeEvent(QCloseEvent *event)
Settings::setValue(Settings::Gui::WindowGeometry, geometry()); Settings::setValue(Settings::Gui::WindowGeometry, geometry());
qDebug() << "hiding to tray"; qDebug() << "hiding to tray";
hide(); hide();
return true;
}
void MainWindow::closeEvent(QCloseEvent *event)
{
if (event->spontaneous() && maybeHideToTray()) {
event->ignore(); event->ignore();
return; return;
} }

View file

@ -122,6 +122,7 @@ private:
void handlePeerFingerprint(const QString &fingerprint); void handlePeerFingerprint(const QString &fingerprint);
void handleMissingKeyboardLayouts(const QString &layouts); void handleMissingKeyboardLayouts(const QString &layouts);
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
bool maybeHideToTray();
void secureSocket(bool secureSocket); void secureSocket(bool secureSocket);
void connectSlots(); void connectSlots();
void handleLogLine(const QString &line); void handleLogLine(const QString &line);

View file

@ -8,9 +8,12 @@
#include <QString> #include <QString>
#include <functional>
void requestOSXNotificationPermission(); void requestOSXNotificationPermission();
bool isOSXDevelopmentBuild(); bool isOSXDevelopmentBuild();
bool showOSXNotification(const QString &title, const QString &body); bool showOSXNotification(const QString &title, const QString &body);
bool isOSXInterfaceStyleDark(); bool isOSXInterfaceStyleDark();
void forceAppActive(); void forceAppActive();
void macOSNativeHide(); void macOSNativeHide();
void installQuitHandler(std::function<bool()> shouldQuit);

View file

@ -13,12 +13,19 @@
#import <UserNotifications/UNNotificationContent.h> #import <UserNotifications/UNNotificationContent.h>
#import <UserNotifications/UNNotificationTrigger.h> #import <UserNotifications/UNNotificationTrigger.h>
#import <UserNotifications/UNUserNotificationCenter.h> #import <UserNotifications/UNUserNotificationCenter.h>
#import <objc/runtime.h>
#import <QtGlobal> #import <QtGlobal>
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" #pragma clang diagnostic ignored "-Wdeprecated-declarations"
namespace {
std::function<bool()> s_shouldQuit;
IMP s_originalShouldTerminate = nullptr;
BOOL s_isSystemShuttingDown = NO;
} // namespace
void requestOSXNotificationPermission() void requestOSXNotificationPermission()
{ {
#if OSX_DEPLOYMENT_TARGET >= 1014 #if OSX_DEPLOYMENT_TARGET >= 1014
@ -107,3 +114,44 @@ void macOSNativeHide()
[NSApp hide:nil]; [NSApp hide:nil];
[[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyAccessory]; [[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<ShouldTerminateFn>(s_originalShouldTerminate)(self, _cmd, sender);
}
return NSTerminateNow;
}
void installQuitHandler(std::function<bool()> 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;
}];
}