From 3e2a2c9cd4d8e6b5bdac06ef465bc7fd26399fef Mon Sep 17 00:00:00 2001 From: Philip Kaufmann Date: Thu, 10 Apr 2014 08:19:58 +0200 Subject: [PATCH] [Qt] catch Windows shutdown events while client is running - prevents unsafe shutdowns on Windows, which is known to be able to cause problems with wallet.dat - if a users ends a Windows session, this will initiate a client shutdown and show a Windows dialog, that tells the user what is going on (for Windows Vista and higher it will even show a reason for blocking the Windows session end) --- src/qt/Makefile.am | 6 ++-- src/qt/bitcoin.cpp | 22 +++++++++++++- src/qt/winshutdownmonitor.cpp | 57 +++++++++++++++++++++++++++++++++++ src/qt/winshutdownmonitor.h | 29 ++++++++++++++++++ 4 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 src/qt/winshutdownmonitor.cpp create mode 100644 src/qt/winshutdownmonitor.h diff --git a/src/qt/Makefile.am b/src/qt/Makefile.am index c28018060..b7f1197b7 100644 --- a/src/qt/Makefile.am +++ b/src/qt/Makefile.am @@ -212,7 +212,8 @@ BITCOIN_QT_H = \ walletframe.h \ walletmodel.h \ walletmodeltransaction.h \ - walletview.h + walletview.h \ + winshutdownmonitor.h RES_ICONS = \ res/icons/add.png \ @@ -286,7 +287,8 @@ BITCOIN_QT_CPP = \ rpcconsole.cpp \ splashscreen.cpp \ trafficgraphwidget.cpp \ - utilitydialog.cpp + utilitydialog.cpp \ + winshutdownmonitor.cpp if ENABLE_WALLET BITCOIN_QT_CPP += \ diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 08d65071a..6543455d6 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -15,6 +15,7 @@ #include "optionsmodel.h" #include "splashscreen.h" #include "utilitydialog.h" +#include "winshutdownmonitor.h" #ifdef ENABLE_WALLET #include "paymentserver.h" #include "walletmodel.h" @@ -189,6 +190,9 @@ public: /// Get process return value int getReturnValue() { return returnValue; } + /// Get window identifier of QMainWindow (BitcoinGUI) + WId getMainWinId() const; + public slots: void initializeResult(int retval); void shutdownResult(int retval); @@ -458,6 +462,14 @@ void BitcoinApplication::handleRunawayException(const QString &message) ::exit(1); } +WId BitcoinApplication::getMainWinId() const +{ + if (!window) + return 0; + + return window->winId(); +} + #ifndef BITCOIN_QT_TEST int main(int argc, char *argv[]) { @@ -572,10 +584,15 @@ int main(int argc, char *argv[]) /// 9. Main GUI initialization // Install global event filter that makes sure that long tooltips can be word-wrapped app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app)); - // Install qDebug() message handler to route to debug.log #if QT_VERSION < 0x050000 + // Install qDebug() message handler to route to debug.log qInstallMsgHandler(DebugMessageHandler); #else +#if defined(Q_OS_WIN) + // Install global event filter for processing Windows session related Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION) + qApp->installNativeEventFilter(new WinShutdownMonitor()); +#endif + // Install qDebug() message handler to route to debug.log qInstallMessageHandler(DebugMessageHandler); #endif // Load GUI settings from QSettings @@ -591,6 +608,9 @@ int main(int argc, char *argv[]) { app.createWindow(isaTestNet); app.requestInitialize(); +#if defined(Q_OS_WIN) && QT_VERSION >= 0x050000 + WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("Bitcoin Core did't yet exit safely..."), (HWND)app.getMainWinId()); +#endif app.exec(); app.requestShutdown(); app.exec(); diff --git a/src/qt/winshutdownmonitor.cpp b/src/qt/winshutdownmonitor.cpp new file mode 100644 index 000000000..b7526f0ae --- /dev/null +++ b/src/qt/winshutdownmonitor.cpp @@ -0,0 +1,57 @@ +// Copyright (c) 2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "winshutdownmonitor.h" + +#if defined(Q_OS_WIN) && QT_VERSION >= 0x050000 +#include "init.h" + +#include + +#include + +// If we don't want a message to be processed by Qt, return true and set result to +// the value that the window procedure should return. Otherwise return false. +bool WinShutdownMonitor::nativeEventFilter(const QByteArray &eventType, void *pMessage, long *pnResult) +{ + Q_UNUSED(eventType); + + MSG *pMsg = static_cast(pMessage); + + switch(pMsg->message) + { + case WM_QUERYENDSESSION: + { + // Initiate a client shutdown after receiving a WM_QUERYENDSESSION and block + // Windows session end until we have finished client shutdown. + StartShutdown(); + *pnResult = FALSE; + return true; + } + + case WM_ENDSESSION: + { + *pnResult = FALSE; + return true; + } + } + + return false; +} + +void WinShutdownMonitor::registerShutdownBlockReason(const QString& strReason, const HWND& mainWinId) +{ + typedef BOOL (WINAPI *PSHUTDOWNBRCREATE)(HWND, LPCWSTR); + PSHUTDOWNBRCREATE shutdownBRCreate = (PSHUTDOWNBRCREATE)GetProcAddress(GetModuleHandleA("User32.dll"), "ShutdownBlockReasonCreate"); + if (shutdownBRCreate == NULL) { + qDebug() << "registerShutdownBlockReason : GetProcAddress for ShutdownBlockReasonCreate failed"; + return; + } + + if (shutdownBRCreate(mainWinId, strReason.toStdWString().c_str())) + qDebug() << "registerShutdownBlockReason : Successfully registered: " + strReason; + else + qDebug() << "registerShutdownBlockReason : Failed to register: " + strReason; +} +#endif diff --git a/src/qt/winshutdownmonitor.h b/src/qt/winshutdownmonitor.h new file mode 100644 index 000000000..4c76d2c81 --- /dev/null +++ b/src/qt/winshutdownmonitor.h @@ -0,0 +1,29 @@ +// Copyright (c) 2014 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef WINSHUTDOWNMONITOR_H +#define WINSHUTDOWNMONITOR_H + +#ifdef WIN32 +#include +#include + +#if QT_VERSION >= 0x050000 +#include // for HWND + +#include + +class WinShutdownMonitor : public QAbstractNativeEventFilter +{ +public: + /** Implements QAbstractNativeEventFilter interface for processing Windows messages */ + bool nativeEventFilter(const QByteArray &eventType, void *pMessage, long *pnResult); + + /** Register the reason for blocking shutdown on Windows to allow clean client exit */ + static void registerShutdownBlockReason(const QString& strReason, const HWND& mainWinId); +}; +#endif +#endif + +#endif // WINSHUTDOWNMONITOR_H