Propagate application termination requests through QPA

Instead of having each platform plugin deal with application termination
in their own weird ways, we now have a QPA API to signal that the system
requested the application to terminate.

On the QGuiApplication side this results in a Quit event being sent to
the application, which triggers the default behavior of closing all app
windows, and then finally calling QCoreApplication::quit().

The quit event replaces the misuse of a close event being sent to the
application. Close events are documented as being sent to windows.

The close events that are sent to individual windows as part of the
quit process can be ignored. This will skip the final quit() of
the application, and also inform the platform that the quit was
not accepted, in case that should be propagated further.

In the future the logic for closing windows should be unified
between the various approaches in closeAllWindows, shouldQuit,
and friends.

Change-Id: I0ed7f1c0d3f0bf1a755e1dd4066e1575fc3a28e1
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
This commit is contained in:
Tor Arne Vestbø 2019-10-14 15:58:50 +02:00
parent 2967510213
commit 1b6db18494
9 changed files with 62 additions and 35 deletions

View File

@ -1863,7 +1863,20 @@ bool QGuiApplication::event(QEvent *e)
{ {
if(e->type() == QEvent::LanguageChange) { if(e->type() == QEvent::LanguageChange) {
setLayoutDirection(qt_detectRTLLanguage()?Qt::RightToLeft:Qt::LeftToRight); setLayoutDirection(qt_detectRTLLanguage()?Qt::RightToLeft:Qt::LeftToRight);
} else if (e->type() == QEvent::Quit) {
// Close open windows. This is done in order to deliver de-expose
// events while the event loop is still running.
for (QWindow *topLevelWindow : QGuiApplication::topLevelWindows()) {
// Already closed windows will not have a platform window, skip those
if (!topLevelWindow->handle())
continue;
if (!topLevelWindow->close()) {
e->ignore();
return true;
}
}
} }
return QCoreApplication::event(e); return QCoreApplication::event(e);
} }
@ -1940,6 +1953,9 @@ void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePriv
QWindowSystemInterfacePrivate::ApplicationStateChangedEvent * changeEvent = static_cast<QWindowSystemInterfacePrivate::ApplicationStateChangedEvent *>(e); QWindowSystemInterfacePrivate::ApplicationStateChangedEvent * changeEvent = static_cast<QWindowSystemInterfacePrivate::ApplicationStateChangedEvent *>(e);
QGuiApplicationPrivate::setApplicationState(changeEvent->newState, changeEvent->forcePropagate); } QGuiApplicationPrivate::setApplicationState(changeEvent->newState, changeEvent->forcePropagate); }
break; break;
case QWindowSystemInterfacePrivate::ApplicationTermination:
QGuiApplicationPrivate::processApplicationTermination(e);
break;
case QWindowSystemInterfacePrivate::FlushEvents: { case QWindowSystemInterfacePrivate::FlushEvents: {
QWindowSystemInterfacePrivate::FlushEventsEvent *flushEventsEvent = static_cast<QWindowSystemInterfacePrivate::FlushEventsEvent *>(e); QWindowSystemInterfacePrivate::FlushEventsEvent *flushEventsEvent = static_cast<QWindowSystemInterfacePrivate::FlushEventsEvent *>(e);
QWindowSystemInterface::deferredFlushWindowSystemEvents(flushEventsEvent->flags); } QWindowSystemInterface::deferredFlushWindowSystemEvents(flushEventsEvent->flags); }
@ -3489,6 +3505,13 @@ bool QGuiApplicationPrivate::tryCloseRemainingWindows(QWindowList processedWindo
return true; return true;
} }
void QGuiApplicationPrivate::processApplicationTermination(QWindowSystemInterfacePrivate::WindowSystemEvent *windowSystemEvent)
{
QEvent event(QEvent::Quit);
QGuiApplication::sendSpontaneousEvent(QGuiApplication::instance(), &event);
windowSystemEvent->eventAccepted = event.isAccepted();
}
/*! /*!
\since 5.2 \since 5.2
\fn Qt::ApplicationState QGuiApplication::applicationState() \fn Qt::ApplicationState QGuiApplication::applicationState()

View File

@ -141,6 +141,8 @@ public:
static void processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e); static void processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e);
static void processApplicationTermination(QWindowSystemInterfacePrivate::WindowSystemEvent *e);
static void updateFilteredScreenOrientation(QScreen *screen); static void updateFilteredScreenOrientation(QScreen *screen);
static void reportScreenOrientationChange(QScreen *screen); static void reportScreenOrientationChange(QScreen *screen);
static void processScreenOrientationChange(QWindowSystemInterfacePrivate::ScreenOrientationEvent *e); static void processScreenOrientationChange(QWindowSystemInterfacePrivate::ScreenOrientationEvent *e);

View File

@ -285,6 +285,12 @@ QT_DEFINE_QPA_EVENT_HANDLER(void, handleApplicationStateChanged, Qt::Application
QWindowSystemInterfacePrivate::handleWindowSystemEvent<Delivery>(e); QWindowSystemInterfacePrivate::handleWindowSystemEvent<Delivery>(e);
} }
QT_DEFINE_QPA_EVENT_HANDLER(bool, handleApplicationTermination)
{
auto *e = new QWindowSystemInterfacePrivate::WindowSystemEvent(QWindowSystemInterfacePrivate::ApplicationTermination);
return QWindowSystemInterfacePrivate::handleWindowSystemEvent<Delivery>(e);
}
QWindowSystemInterfacePrivate::GeometryChangeEvent::GeometryChangeEvent(QWindow *window, const QRect &newGeometry) QWindowSystemInterfacePrivate::GeometryChangeEvent::GeometryChangeEvent(QWindow *window, const QRect &newGeometry)
: WindowSystemEvent(GeometryChange) : WindowSystemEvent(GeometryChange)
, window(window) , window(window)

View File

@ -215,6 +215,9 @@ public:
template<typename Delivery = QWindowSystemInterface::DefaultDelivery> template<typename Delivery = QWindowSystemInterface::DefaultDelivery>
static void handleApplicationStateChanged(Qt::ApplicationState newState, bool forcePropagate = false); static void handleApplicationStateChanged(Qt::ApplicationState newState, bool forcePropagate = false);
template<typename Delivery = QWindowSystemInterface::DefaultDelivery>
static bool handleApplicationTermination();
#if QT_CONFIG(draganddrop) #if QT_CONFIG(draganddrop)
#if QT_DEPRECATED_SINCE(5, 11) #if QT_DEPRECATED_SINCE(5, 11)
QT_DEPRECATED static QPlatformDragQtResponse handleDrag(QWindow *window, const QMimeData *dropData, QT_DEPRECATED static QPlatformDragQtResponse handleDrag(QWindow *window, const QMimeData *dropData,

View File

@ -99,7 +99,8 @@ public:
ApplicationStateChanged = 0x19, ApplicationStateChanged = 0x19,
FlushEvents = 0x20, FlushEvents = 0x20,
WindowScreenChanged = 0x21, WindowScreenChanged = 0x21,
SafeAreaMarginsChanged = 0x22 SafeAreaMarginsChanged = 0x22,
ApplicationTermination = 0x23
}; };
class WindowSystemEvent { class WindowSystemEvent {

View File

@ -88,10 +88,13 @@
#include <qpa/qwindowsysteminterface.h> #include <qpa/qwindowsysteminterface.h>
#include <qwindowdefs.h> #include <qwindowdefs.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaApplication, "qt.qpa.application");
QT_END_NAMESPACE
QT_USE_NAMESPACE QT_USE_NAMESPACE
@implementation QCocoaApplicationDelegate { @implementation QCocoaApplicationDelegate {
bool startedQuit;
NSObject <NSApplicationDelegate> *reflectionDelegate; NSObject <NSApplicationDelegate> *reflectionDelegate;
bool inLaunch; bool inLaunch;
} }
@ -150,30 +153,20 @@ QT_USE_NAMESPACE
// No event loop is executing. This probably means that Qt is used as a plugin, // No event loop is executing. This probably means that Qt is used as a plugin,
// or as a part of a native Cocoa application. In any case it should be fine to // or as a part of a native Cocoa application. In any case it should be fine to
// terminate now. // terminate now.
qCDebug(lcQpaApplication) << "No running event loops, terminating now";
return NSTerminateNow; return NSTerminateNow;
} }
QCloseEvent ev; if (!QWindowSystemInterface::handleApplicationTermination<QWindowSystemInterface::SynchronousDelivery>()) {
QGuiApplication::sendEvent(qGuiApp, &ev); qCDebug(lcQpaApplication) << "Application termination canceled";
if (!ev.isAccepted())
return NSTerminateCancel; return NSTerminateCancel;
if (!startedQuit) {
startedQuit = true;
// Close open windows. This is done in order to deliver de-expose
// events while the event loop is still running.
for (QWindow *topLevelWindow : QGuiApplication::topLevelWindows()) {
// Already closed windows will not have a platform window, skip those
if (!topLevelWindow->handle())
continue;
QWindowSystemInterface::handleCloseEvent<QWindowSystemInterface::SynchronousDelivery>(topLevelWindow);
}
QGuiApplication::exit(0);
startedQuit = false;
} }
// Even if the application termination was accepted by the application we can't
// return NSTerminateNow, as that would trigger AppKit to ultimately call exit().
// We need to ensure that the runloop continues spinning so that we can return
// from our own event loop back to main(), and exit from there.
qCDebug(lcQpaApplication) << "Termination accepted, but returning to runloop for exit through main()";
return NSTerminateCancel; return NSTerminateCancel;
} }

View File

@ -42,6 +42,8 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QFileOpenEvent> #include <QFileOpenEvent>
#include <qpa/qwindowsysteminterface.h>
#include <Entry.h> #include <Entry.h>
#include <Path.h> #include <Path.h>
@ -52,8 +54,7 @@ QHaikuApplication::QHaikuApplication(const char *signature)
bool QHaikuApplication::QuitRequested() bool QHaikuApplication::QuitRequested()
{ {
QEvent quitEvent(QEvent::Quit); QWindowSystemInterface::handleApplicationTermination<QWindowSystemInterface::SynchronousDelivery>();
QCoreApplication::sendEvent(QCoreApplication::instance(), &quitEvent);
return true; return true;
} }

View File

@ -42,6 +42,8 @@
#ifndef QT_NO_SESSIONMANAGER #ifndef QT_NO_SESSIONMANAGER
#include <qpa/qwindowsysteminterface.h>
#include <qguiapplication.h> #include <qguiapplication.h>
#include <qdatetime.h> #include <qdatetime.h>
#include <qfileinfo.h> #include <qfileinfo.h>
@ -289,8 +291,7 @@ static void sm_dieCallback(SmcConn smcConn, SmPointer /* clientData */)
if (smcConn != smcConnection) if (smcConn != smcConnection)
return; return;
resetSmState(); resetSmState();
QEvent quitEvent(QEvent::Quit); QWindowSystemInterface::handleApplicationTermination<QWindowSystemInterface::SynchronousDelivery>();
QGuiApplication::sendEvent(qApp, &quitEvent);
} }
static void sm_shutdownCancelledCallback(SmcConn smcConn, SmPointer clientData) static void sm_shutdownCancelledCallback(SmcConn smcConn, SmPointer clientData)

View File

@ -1866,22 +1866,19 @@ void QApplication::aboutQt()
bool QApplication::event(QEvent *e) bool QApplication::event(QEvent *e)
{ {
Q_D(QApplication); Q_D(QApplication);
if(e->type() == QEvent::Close) { if (e->type() == QEvent::Quit) {
QCloseEvent *ce = static_cast<QCloseEvent*>(e);
ce->accept();
closeAllWindows(); closeAllWindows();
for (auto *w : topLevelWidgets()) {
const QWidgetList list = topLevelWidgets();
for (auto *w : list) {
if (w->isVisible() && !(w->windowType() == Qt::Desktop) && !(w->windowType() == Qt::Popup) && if (w->isVisible() && !(w->windowType() == Qt::Desktop) && !(w->windowType() == Qt::Popup) &&
(!(w->windowType() == Qt::Dialog) || !w->parentWidget())) { (!(w->windowType() == Qt::Dialog) || !w->parentWidget())) {
ce->ignore(); e->ignore();
break; return true;
} }
} }
if (ce->isAccepted()) { // Explicitly call QCoreApplication instead of QGuiApplication so that
return true; // we don't let QGuiApplication close any windows we skipped earlier in
} // closeAllWindows(). FIXME: Unify all this close magic through closeAllWindows.
return QCoreApplication::event(e);
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
} else if (e->type() == QEvent::LocaleChange) { } else if (e->type() == QEvent::LocaleChange) {
// on Windows the event propagation is taken care by the // on Windows the event propagation is taken care by the