如何在注销时优雅地退出QApplication

How to quit QApplication gracefully on logout?

本文关键字:退出 QApplication 注销      更新时间:2023-10-16

我有一个带有通知区域图标的应用程序,所以主窗口可能会忽略关闭事件。我正在尝试保存应用程序退出的首选项和历史。我还想在应用程序运行时处理注销。虽然应用程序是跨平台的,但它在GNU/Linux中是最方便/有用的,所以Windows的注销行为就不那么重要了。这是一个最小的可编译示例,用于测试不同桌面环境/窗口管理器的行为:

// 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()

大多数成熟的桌面环境在注销时试图关闭可见窗口。但由于测试应用程序关闭时不退出,注销被中断/取消。如果在窗口不可见的情况下执行注销,它会优雅地退出(就像我想要的那样)。功能不全的桌面环境/窗口管理器忽略仍在运行的应用程序并退出。它们中的大多数甚至不警告应用程序注销,因此文件中既没有"closeEvent()",也没有"onAboutToQuit()",也没有"M::~M()",程序输出被重定向到这些文件。

详细的结果(所有非windows的结果来自Manjaro GNU/Linux):

  1. 完整的桌面环境,如果可见窗口拒绝退出,取消注销,优雅地完成不可见的应用程序:

    closeEvent()
    onAboutToQuit()
    M::~M()
        { KDE, XFCE, Mate, GNOME, Cinnamon }
    
  2. 我不知道为什么onAboutToQuit()存在于日志中,但是M::~M()不在这种情况下…

    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 }

的行为是完全相同的任何组合(GCC 4.9.1或Clang 3.4.2)和(Qt 4.8.6或Qt 5.3.1)。然而,当我在Xubuntu上尝试Qt 4.8和Qt 5.2时,结果有些不同:在XFCE中Qt 5.2没有阻塞-无论主窗口是否可见,应用程序都可以优雅地完成。但是阻塞在Qt 4.8中存在(就像在Manjaro中一样)。

我知道正确处理登出(不阻塞)是可能的,因为有几个应用程序可以很好地做到这一点。它们都有通知区图标,关闭通知区,但不阻止注销。

  • 基于qt的:GoldenDict, Transmission-Qt, Kopete;
  • 基于gts的:Audacious, Pidgin.

我检查了基于qt的源代码,发现在处理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 );
    }
}

所以问题是:

  1. 如何确保我的应用程序不阻止登出,即使主窗口是可见的?

  2. 如何确保在尽可能多的桌面环境/窗口管理器中注销时调用onAboutToQuit()和~M() ?

我怀疑应该听一些系统信号,但我不知道确切的…

QApplication具有与OS会话操作相关的信号-您可以轻松处理它。要了解更多细节,请查看Qt文档会话管理页面