Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to quit QApplication gracefully on logout?

Tags:

c++

linux

qt

qt4

qt5

I have an application with notification area icon, so the main window may ignore close event. I am trying to save preferences and history on application quit. I also want to handle logout when the application is running. While the application is cross-platform, it is most convenient/useful in GNU/Linux, so Windows logout behavior is of much less concern. This is a minimal compilable example that was used for testing behavior of different desktop environments/window managers:

// main.cpp
# include <QApplication>
# include <QMainWindow>
# include <QCloseEvent>
# include <QTimer>
# include <iostream>

class M : public QMainWindow
{
    Q_OBJECT
public:
    ~M();
public slots:
    void onAboutToQuit();
private:
    void closeEvent(QCloseEvent *);
};

M::~M()
{
    std::cout << "M::~M()" << std::endl;
}

void M::onAboutToQuit()
{
    std::cout << "onAboutToQuit()" << std::endl;
}

void M::closeEvent(QCloseEvent * e)
{
    std::cout << "closeEvent()" << std::endl;
    hide();
    QTimer::singleShot(5000, this, SLOT(show()));
    e->ignore();
}

int main(int argc, char * argv[])
{
    QApplication app(argc, argv);

    M m;
    m.setWindowModality(Qt::NonModal);
    m.connect(& app, SIGNAL(aboutToQuit()),
            SLOT(onAboutToQuit()));
    m.show();

    return app.exec();
}

# include "main.moc"

// CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
project(closeeventbug)

option(QT5 "Use Qt5" OFF)

if(QT5)
    find_package(Qt5Core REQUIRED)
    find_package(Qt5Widgets REQUIRED)
else()
    find_package(Qt4 REQUIRED)
    include_directories(${QT_INCLUDES})
endif()

include_directories(${CMAKE_CURRENT_BINARY_DIR})
set(CMAKE_AUTOMOC ON)

add_executable(closeeventbug main.cpp)

if(QT5)
    qt5_use_modules(closeeventbug Core Widgets)
else()
    target_link_libraries(closeeventbug ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY})
endif()

Most full-blown desktop environments on logout attempt to close visible window. But since the test app does not exit when closed, logging out is interrupted/cancelled. If logging out is performed while the window is invisible, it quits gracefully (just like I want it to). Less feature-full desktop environments/window managers ignore the still-running application and exit. Most of them don't even warn the application about logging out, so there is neither "closeEvent()", nor "onAboutToQuit()", nor "M::~M()" in the file, to which the program output is redirected.

The detailed results (all non-Windows results are from Manjaro GNU/Linux):

  1. Full-blown desktop environments that cancel logging out if the visible window refuses to exit, finish invisible application gracefully:

    closeEvent()
    onAboutToQuit()
    M::~M()
        { KDE, XFCE, Mate, GNOME, Cinnamon }
    

    All others don't cancel quit, but some of them try to warn the application.

  2. I have no idea why onAboutToQuit() is present in the log, but M::~M() is not in this case...

    closeEvent()
    onAboutToQuit()
        { Windows7 }
    

3.

   closeEvent()
       { icewm, E17 }

4.

   <nothing in the log>
       { RazorQt, LxDE, LxQt, i3, budgie, fluxbox, awesome, openbox,
         wmii, E16, pekWM, uwm, fvwm, xmonad, spectrwm, windowmaker,
         herbstluftwm, WindowsXP }

The behaviour is exactly the same for any combination of (GCC 4.9.1 OR Clang 3.4.2) AND (Qt 4.8.6 OR Qt 5.3.1). However when I tried Qt 4.8 and Qt 5.2 on Xubuntu, the results were somewhat different: there was no blocking with Qt 5.2 in XFCE - the application finished gracefully regardless of main window visibility. But blocking was present with Qt 4.8 (just the same as in Manjaro).

I know that it is possible to handle logout properly (without blocking) because there are several applications that do that just fine. All of them have notification area icon, are closed to notification area, but don't block logging out.

  • Qt-based: GoldenDict, Transmission-Qt, Kopete;
  • GTK-based: Audacious, Pidgin.

I have examined the source code of Qt-based ones and found nothing special in handling closeEvent:

https://github.com/goldendict/goldendict/blob/master/mainwindow.cc

void MainWindow::closeEvent( QCloseEvent * ev )
{
    if ( cfg.preferences.enableTrayIcon && cfg.preferences.closeToTray )
    {
        ev->ignore();
        hide();
    }
    else
    {
        ev->accept();
        qApp->quit();
    }
}


https://github.com/bfleischer/transmission/blob/master/qt/mainwin.cc

void
TrMainWindow :: closeEvent( QCloseEvent * event )
{
    // if they're using a tray icon, close to the tray
    // instead of exiting
    if( !myPrefs.getBool( Prefs :: SHOW_TRAY_ICON ) )
        event->accept( );
    else {
        toggleWindows( false );
        event->ignore( );
    }
}

void
TrMainWindow :: toggleWindows( bool doShow )
{
    if( !doShow )
    {
        hide( );
    }
    else
    {
        if ( !isVisible( ) ) show( );
        if ( isMinimized( ) ) showNormal( );
        //activateWindow( );
        raise( );
        QApplication::setActiveWindow( this );
    }
}

git clone git://anongit.kde.org/kopete

void KopeteWindow::closeEvent ( QCloseEvent *e )
{
    // if we are not ok to exit on close and we are not shutting down then just do what needs to be done if a
    // window is closed.
    KopeteApplication *app = static_cast<KopeteApplication *> ( kapp );
    if ( !shouldExitOnClose() && !app->isShuttingDown() && !app->sessionSaving() ) {
        // BEGIN of code borrowed from KMainWindow::closeEvent
        // Save settings if auto-save is enabled, and settings have changed
        if ( settingsDirty() && autoSaveSettings() )
            saveAutoSaveSettings();

        if ( queryClose() ) {
            e->accept();
        }
        // END of code borrowed from KMainWindow::closeEvent
        kDebug ( 14000 ) << "just closing because we have a system tray icon";
    }
    else
    {
        kDebug ( 14000 ) << "delegating to KXmlGuiWindow::closeEvent()";
        KXmlGuiWindow::closeEvent ( e );
    }
}

So the questions:

  1. How to ensure that my application does not block logging out even if the main window is visible?

  2. How to ensure that onAboutToQuit() and ~M() are called on logout in as many desktop environments/window managers as possible?

I suspect that some system signals should be listened to, but I have no idea which exactly...

like image 731
vedg Avatar asked Aug 03 '14 19:08

vedg


1 Answers

QApplication has signal related to OS session actions - you can easily handle it. For more details take a look at Qt docs Session Management page

like image 186
Kamil Klimek Avatar answered Oct 22 '22 11:10

Kamil Klimek