Search code examples
windowsqtqt5application-shutdown

Differentiate close event from "X" button and from OS shutdown in Windows with Qt


I have caught the closeEvent from QMainWindow in my application, in order to show a "Are you sure?" popup to the user, if he closes the app with the "X" button.

Unfortunately the popup creates problems if the application is closed from Windows itself during the shutdown.

I would like to differentiate the behaviour if the app was closed using the "X" button or by the OS itself.

Is it possible?

I tried to use the aboutToQuit signal from Qt, but the popup appears before the signal is received from my app. So this signal does not help me...


Solution

  • In windows two messages are send to all windows when shutdown initiated: WM_QUERYENDSESSION and WM_ENDSESSION, they are transformed into QtWindows::QueryEndSessionApplicationEvent and QtWindows::EndSessionApplicationEvent. This events are handled in QWindowsContext::windowsProc: first one calls QApplication::commitData on application instance, which emits commitDataRequest() from session manager instance, and second one emits aboutToQuit() from application instance.

    Relevant part of Qt called Session Management and includes a set of platform specific QSessionManager classes. Here's what documentation recommends:

    Start by connecting a slot to the QGuiApplication::commitDataRequest() signal to enable your application to take part in the graceful logout process. If you are only targeting the Microsoft Windows platform, this is all you can and must provide. Ideally, your application should provide a shutdown dialog.

    Here's example from documentation how you can do this:

    MyMainWidget::MyMainWidget(QWidget *parent)
        :QWidget(parent)
    {
        QGuiApplication::setFallbackSessionManagementEnabled(false);
        connect(qApp, &QGuiApplication::commitDataRequest,
                this, &MyMainWidget::commitData);
    }
    
    void MyMainWidget::commitData(QSessionManager& manager)
    {
        if (manager.allowsInteraction()) {
            int ret = QMessageBox::warning(
                        mainWindow,
                        tr("My Application"),
                        tr("Save changes to document?"),
                        QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
    
            switch (ret) {
            case QMessageBox::Save:
                manager.release();
                if (!saveDocument())
                    manager.cancel();
                break;
            case QMessageBox::Discard:
                break;
            case QMessageBox::Cancel:
            default:
                manager.cancel();
            }
        } else {
            // we did not get permission to interact, then
            // do something reasonable instead
        }
    }