From 6644fe071437517d7822486c838b11151ff0ea5d Mon Sep 17 00:00:00 2001 From: sledgehammer999 Date: Thu, 16 Apr 2015 00:47:59 +0300 Subject: [PATCH] Tell Windows to wait during shutdown by creating a ShutdownBlockReason. Huge thanks to paolo-sz for bringing this to my attention, for the various patches he tried to submit and for testing. See issue #1984 for complete history. Closes #1535. --- src/app/application.cpp | 79 +++++++++++++++++++++++++++++++++++++++-- src/app/application.h | 10 ++++++ src/gui/mainwindow.cpp | 21 +++++++++-- src/gui/mainwindow.h | 1 + 4 files changed, 106 insertions(+), 5 deletions(-) diff --git a/src/app/application.cpp b/src/app/application.cpp index e7ea94edd..cc9cd95c7 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -37,6 +37,7 @@ #ifdef Q_OS_WIN #include #include +#include #endif // Q_OS_WIN #ifdef Q_OS_MAC #include @@ -77,7 +78,10 @@ Application::Application(const QString &id, int &argc, char **argv) #ifndef DISABLE_GUI setStyleSheet("QStatusBar::item { border-width: 0; }"); setQuitOnLastWindowClosed(false); -#endif +#ifdef Q_OS_WIN + connect(this, SIGNAL(commitDataRequest(QSessionManager &)), this, SLOT(shutdownCleanup(QSessionManager &)), Qt::DirectConnection); +#endif // Q_OS_WIN +#endif // DISABLE_GUI connect(this, SIGNAL(messageReceived(const QString &)), SLOT(processMessage(const QString &))); connect(this, SIGNAL(aboutToQuit()), SLOT(cleanup())); @@ -286,11 +290,69 @@ void Application::initializeTranslation() #endif } +#if (!defined(DISABLE_GUI) && defined(Q_OS_WIN)) +void Application::shutdownCleanup(QSessionManager &manager) +{ + // This is only needed for a special case on Windows XP. + // (but is called for every Windows version) + // If a process takes too much time to exit during OS + // shutdown, the OS presents a dialog to the user. + // That dialog tells the user that qbt is blocking the + // shutdown, it shows a progress bar and it offers + // a "Terminate Now" button for the user. However, + // after the progress bar has reached 100% another button + // is offered to the user reading "Cancel". With this the + // user can cancel the **OS** shutdown. If we don't do + // the cleanup by handling the commitDataRequest() signal + // and the user clicks "Cancel", it will result in qbt being + // killed and the shutdown proceeding instead. Apparently + // aboutToQuit() is emitted too late in the shutdown process. + cleanup(); + + // According to the qt docs we shouldn't call quit() inside a slot. + // aboutToQuit() is never emitted if the user hits "Cancel" in + // the above dialog. + QTimer::singleShot(0, qApp, SLOT(quit())); +} +#endif + void Application::cleanup() { #ifndef DISABLE_GUI - delete m_window; -#endif +#ifdef Q_OS_WIN + // cleanup() can be called multiple times during shutdown. We only need it once. + static bool alreadyDone = false; + + if (alreadyDone) + return; + alreadyDone = true; +#endif // Q_OS_WIN + + // Hide the window and not leave it on screen as + // unresponsive. Also for Windows take the WinId + // after it's hidden, because hide() may cause a + // WinId change. + m_window->hide(); + +#ifdef Q_OS_WIN + typedef BOOL (WINAPI *PSHUTDOWNBRCREATE)(HWND, LPCWSTR); + PSHUTDOWNBRCREATE shutdownBRCreate = (PSHUTDOWNBRCREATE)::GetProcAddress(::GetModuleHandleW(L"User32.dll"), "ShutdownBlockReasonCreate"); + // Only available on Vista+ + if (shutdownBRCreate) + shutdownBRCreate((HWND)m_window->effectiveWinId(), tr("Saving torrent progress...").toStdWString().c_str()); +#endif // Q_OS_WIN + + // Do manual cleanup in MainWindow to force widgets + // to save their Preferences, stop all timers and + // delete as many widgets as possible to leave only + // a 'shell' MainWindow. + // We need a valid window handle for Windows Vista+ + // otherwise the system shutdown will continue even + // though we created a ShutdownBlockReason + m_window->cleanup(); + +#endif // DISABLE_GUI + #ifndef DISABLE_WEBUI delete m_webui; #endif @@ -298,4 +360,15 @@ void Application::cleanup() TorrentPersistentData::drop(); Preferences::drop(); Logger::drop(); +#ifndef DISABLE_GUI +#ifdef Q_OS_WIN + typedef BOOL (WINAPI *PSHUTDOWNBRDESTROY)(HWND); + PSHUTDOWNBRDESTROY shutdownBRDestroy = (PSHUTDOWNBRDESTROY)::GetProcAddress(::GetModuleHandleW(L"User32.dll"), "ShutdownBlockReasonDestroy"); + // Only available on Vista+ + if (shutdownBRDestroy) + shutdownBRDestroy((HWND)m_window->effectiveWinId()); +#endif // Q_OS_WIN + delete m_window; +#endif + } diff --git a/src/app/application.h b/src/app/application.h index 6dd03ac74..c0491a291 100644 --- a/src/app/application.h +++ b/src/app/application.h @@ -38,6 +38,13 @@ #include "qtsingleapplication.h" typedef QtSingleApplication BaseApplication; class MainWindow; + +#ifdef Q_OS_WIN +QT_BEGIN_NAMESPACE +class QSessionManager; +QT_END_NAMESPACE +#endif // Q_OS_WIN + #else #include "qtsinglecoreapplication.h" typedef QtSingleCoreApplication BaseApplication; @@ -71,6 +78,9 @@ protected: private slots: void processMessage(const QString &message); void cleanup(); +#if (!defined(DISABLE_GUI) && defined(Q_OS_WIN)) + void shutdownCleanup(QSessionManager &manager); +#endif private: bool m_running; diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 65c4ac622..f7a86a381 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -369,8 +369,6 @@ MainWindow::MainWindow(QWidget *parent) MainWindow::~MainWindow() { - // Save window size, columns size - writeSettings(); #ifdef Q_OS_MAC // Workaround to avoid bug http://bugreports.qt.nokia.com/browse/QTBUG-7305 setUnifiedTitleAndToolBarOnMac(false); @@ -592,6 +590,25 @@ void MainWindow::writeSettings() properties->saveSettings(); } +void MainWindow::cleanup() +{ + writeSettings(); + + delete executable_watcher; + guiUpdater->stop(); + if (systrayCreator) + systrayCreator->stop(); + if (preventTimer) + preventTimer->stop(); + programUpdateTimer.stop(); + delete search_filter; + delete searchFilterAct; + delete tabs; // this seems enough to also delete all contained widgets + delete status_bar; + delete m_pwr; + delete toolbarMenu; +} + void MainWindow::readSettings() { const Preferences* const pref = Preferences::instance(); diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index f7a9b9d69..c3ebdd3a9 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -86,6 +86,7 @@ public slots: void updateAltSpeedsBtn(bool alternative); void updateNbTorrents(); void activate(); + void cleanup(); protected slots: // GUI related slots