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.
This commit is contained in:
sledgehammer999 2015-04-16 00:47:59 +03:00
parent 6c1740f78f
commit 6644fe0714
4 changed files with 106 additions and 5 deletions

View file

@ -37,6 +37,7 @@
#ifdef Q_OS_WIN
#include <Windows.h>
#include <QSharedMemory>
#include <QSessionManager>
#endif // Q_OS_WIN
#ifdef Q_OS_MAC
#include <QFileOpenEvent>
@ -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
}

View file

@ -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;

View file

@ -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();

View file

@ -86,6 +86,7 @@ public slots:
void updateAltSpeedsBtn(bool alternative);
void updateNbTorrents();
void activate();
void cleanup();
protected slots:
// GUI related slots