diff --git a/.gitmodules b/.gitmodules index e9830f994..b57856bd3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "AuthAPI"] path = AuthAPI url = https://github.com/autheid/AuthAPI +[submodule "breakpad/breakpad"] + path = breakpad/breakpad + url = https://github.com/google/breakpad.git diff --git a/BlockSettleApp/CMakeLists.txt b/BlockSettleApp/CMakeLists.txt index 77bdb4422..27cdd587c 100644 --- a/BlockSettleApp/CMakeLists.txt +++ b/BlockSettleApp/CMakeLists.txt @@ -4,6 +4,12 @@ FILE(GLOB SOURCES *.cpp) FILE(GLOB HEADERS *.h) INCLUDE_DIRECTORIES( ${COMMON_UI_LIB_INCLUDE_DIR} ) + +if(WIN32) + set(SOURCES ${SOURCES} ../breakpad/qtsystemexceptionhandler.cpp) + set(HEADERS ${HEADERS} ../breakpad/qtsystemexceptionhandler.h) +endif() + INCLUDE_DIRECTORIES( ${COMMON_LIB_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${BLOCKSETTLE_UI_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${BS_NETWORK_INCLUDE_DIR} ) @@ -14,6 +20,7 @@ INCLUDE_DIRECTORIES( ${CRYPTO_LIB_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${BOTAN_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${Qt5Svg_INCLUDE_DIRS} ) + IF ( APPLE ) SET( BUNDLE_NAME "BlockSettle Terminal" ) @@ -58,4 +65,13 @@ TARGET_LINK_LIBRARIES( ${BLOCKSETTLE_APP_NAME} ${QT_LIBS} ${OS_SPECIFIC_LIBS} ${OPENSSL_LIBS} +) + +if(WIN32) + target_include_directories(${BLOCKSETTLE_APP_NAME} + PRIVATE ${CMAKE_BINARY_DIR}/common/BlockSettleUILib # for TerminalVersion.h + PRIVATE ../breakpad ) + target_compile_definitions(${BLOCKSETTLE_APP_NAME} PRIVATE -DENABLE_QT_BREAKPAD) + target_link_libraries(${BLOCKSETTLE_APP_NAME} libbreakpad) +endif() diff --git a/BlockSettleApp/main.cpp b/BlockSettleApp/main.cpp index 08dc8e804..3650b6251 100644 --- a/BlockSettleApp/main.cpp +++ b/BlockSettleApp/main.cpp @@ -22,6 +22,11 @@ #include "btc/ecc.h" #include "AppNap.h" +#include "TerminalVersion.h" + +#ifdef ENABLE_QT_BREAKPAD +#include "qtsystemexceptionhandler.h" +#endif #ifdef USE_QWindowsIntegrationPlugin Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) @@ -171,6 +176,16 @@ static int GuiApp(int &argc, char** argv) QApplication app(argc, argv); #endif + app.setApplicationVersion(QLatin1String(TERMINAL_VERSION_STRING)); + +#ifdef ENABLE_QT_BREAKPAD + // uncomment this to test the crash handler + //QTimer::singleShot(13000, []{ QtSystemExceptionHandler::crash(); }); + QtSystemExceptionHandler exceptionHandler(app.applicationDirPath()); +#endif + + QApplication::setOrganizationName(QLatin1String("BlockSettle")); + QApplication::setApplicationName(QLatin1String("Terminal")); QApplication::setQuitOnLastWindowClosed(false); @@ -255,6 +270,7 @@ static int GuiApp(int &argc, char** argv) splashScreen.setGeometry(splashGeometry); splashScreen.show(); + app.processEvents(); #ifdef NDEBUG @@ -264,6 +280,9 @@ static int GuiApp(int &argc, char** argv) #endif } +#include +#include + int main(int argc, char** argv) { srand(std::time(nullptr)); diff --git a/BlockSettleSigner/CMakeLists.txt b/BlockSettleSigner/CMakeLists.txt index 0bfc40538..6bd60c080 100644 --- a/BlockSettleSigner/CMakeLists.txt +++ b/BlockSettleSigner/CMakeLists.txt @@ -14,6 +14,10 @@ LIST(APPEND HEADERS ${HEADERS_QML}) FILE(GLOB RESOURCE_FILES *.qrc) +if(WIN32) + set(SOURCES ${SOURCES} ../breakpad/qtsystemexceptionhandler.cpp) + set(HEADERS ${HEADERS} ../breakpad/qtsystemexceptionhandler.h) +endif() INCLUDE_DIRECTORIES( interfaces/GUI_QML ) INCLUDE_DIRECTORIES( ${COMMON_LIB_INCLUDE_DIR} ) @@ -81,3 +85,12 @@ TARGET_LINK_LIBRARIES(${SIGNER_APP_NAME} ${OS_SPECIFIC_LIBS} ${OPENSSL_LIBS} ) + +if(WIN32) + target_include_directories(${SIGNER_APP_NAME} + PRIVATE ${CMAKE_BINARY_DIR}/common/BlockSettleUILib # for TerminalVersion.h + PRIVATE ../breakpad + ) + target_compile_definitions(${SIGNER_APP_NAME} PRIVATE -DENABLE_QT_BREAKPAD) + target_link_libraries(${SIGNER_APP_NAME} libbreakpad) +endif() diff --git a/BlockSettleSigner/main.cpp b/BlockSettleSigner/main.cpp index fbe292840..c738b3b38 100644 --- a/BlockSettleSigner/main.cpp +++ b/BlockSettleSigner/main.cpp @@ -35,6 +35,11 @@ #include "QmlBridge.h" #include "AppNap.h" +#include "TerminalVersion.h" + +#ifdef ENABLE_QT_BREAKPAD +#include "qtsystemexceptionhandler.h" +#endif Q_DECLARE_METATYPE(std::string) Q_DECLARE_METATYPE(std::vector) @@ -181,6 +186,14 @@ static int QMLApp(int argc, char **argv QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication app(argc, argv); + app.setApplicationVersion(QLatin1String(TERMINAL_VERSION_STRING)); + +#ifdef ENABLE_QT_BREAKPAD + // uncomment this to test the crash handler + //QTimer::singleShot(20000, []{ QtSystemExceptionHandler::crash(); }); + QtSystemExceptionHandler exceptionHandler(app.applicationDirPath()); +#endif + QApplication::setOrganizationDomain(QLatin1String("blocksettle.com")); #ifdef __linux__ // Needed for consistency (headless now uses company name in lowercase on Linux) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e176c5e9..d27975417 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,8 @@ endif() option(BSTERMINAL_SHARED_LIBS "Build shared libraries" OFF) +option(ENABLE_CRASH_HANDLER "Enable the crash handler" OFF) + add_definitions(-DSTATIC_BUILD) add_definitions(-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG) @@ -798,6 +800,11 @@ ADD_SUBDIRECTORY( Celer ) ADD_SUBDIRECTORY( common/Blocksettle_proto ) ADD_SUBDIRECTORY( AuthAPI ) +if(WIN32) + add_subdirectory(breakpad) + add_subdirectory(qtcrashhandler) +endif() + ADD_SUBDIRECTORY(BlockSettleApp) ADD_SUBDIRECTORY(BlockSettleSigner) diff --git a/breakpad/CMakeLists.txt b/breakpad/CMakeLists.txt new file mode 100644 index 000000000..c0353a529 --- /dev/null +++ b/breakpad/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.0.2) +project(libbreakpad CXX) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_definitions( + -DNOMINMAX + -DUNICODE + -DWIN32_LEAN_AND_MEAN + -D_CRT_SECURE_NO_WARNINGS + -D_CRT_SECURE_NO_DEPRECATE + -D_CRT_NONSTDC_NO_DEPRECATE + -D_LIBCPP_VERSION +) + +file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/windows/*.cc breakpad/src/common/windows/*.cc) + +include_directories("$ENV{VSINSTALLDIR}/DIA SDK/include") + +file(GLOB LIBBREAKPAD_COMMON_SOURCES breakpad/src/common/*.cc breakpad/src/common/*.c breakpad/src/client/*.cc) + +list(APPEND LIBBREAKPAD_CLIENT_SOURCES ${LIBBREAKPAD_COMMON_SOURCES}) + +list(FILTER LIBBREAKPAD_CLIENT_SOURCES EXCLUDE REGEX "/tests|/unittests|_unittest") + +list(FILTER LIBBREAKPAD_CLIENT_SOURCES EXCLUDE REGEX "language.cc|path_helper.cc|stabs_to_module.cc|stabs_reader.cc|minidump_file_writer.cc") + +add_library(libbreakpad ${LIBBREAKPAD_CLIENT_SOURCES}) + +target_link_libraries(libbreakpad PRIVATE wininet.lib) + +target_include_directories(libbreakpad + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src +) diff --git a/breakpad/breakpad b/breakpad/breakpad new file mode 160000 index 000000000..db1cda265 --- /dev/null +++ b/breakpad/breakpad @@ -0,0 +1 @@ +Subproject commit db1cda26539c711c3da7ed4d410dfe8190e89b8f diff --git a/breakpad/qtsystemexceptionhandler.cpp b/breakpad/qtsystemexceptionhandler.cpp new file mode 100644 index 000000000..32b3dabe5 --- /dev/null +++ b/breakpad/qtsystemexceptionhandler.cpp @@ -0,0 +1,258 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "qtsystemexceptionhandler.h" + +//#include +//#include + +#include +#include +#include +#include + +#if defined(Q_OS_LINUX) +# include "client/linux/handler/exception_handler.h" +#elif defined(Q_OS_WIN) +# include "client/windows/handler/exception_handler.h" +#elif defined(Q_OS_MACOS) +# include "client/mac/handler/exception_handler.h" +#endif + +#if defined(Q_OS_LINUX) +static bool exceptionHandlerCallback(const google_breakpad::MinidumpDescriptor& descriptor, + void* /*context*/, + bool succeeded) +{ + if (!succeeded) + return succeeded; + + const QStringList argumentList = { + QString::fromLocal8Bit(descriptor.path()), + QString::number(QtSystemExceptionHandler::startTime().toTime_t()), + QCoreApplication::organizationName(), + QCoreApplication::applicationName(), + QCoreApplication::applicationVersion(), + QtSystemExceptionHandler::plugins(), + QtSystemExceptionHandler::buildVersion(), + QCoreApplication::applicationFilePath() + }; + + qWarning() << "[execute crash handler]" << QtSystemExceptionHandler::crashHandlerPath(); + qWarning() << argumentList; + return !QProcess::execute(QtSystemExceptionHandler::crashHandlerPath(), argumentList); +} +#elif defined(Q_OS_MACOS) +static bool exceptionHandlerCallback(const char *dump_dir, + const char *minidump_id, + void *context, + bool succeeded) +{ + Q_UNUSED(context); + + if (!succeeded) + return succeeded; + + const QString path = QString::fromLocal8Bit(dump_dir) + '/' + + QString::fromLocal8Bit(minidump_id) + ".dmp"; + const QStringList argumentList = { + path, + QString::number(QtSystemExceptionHandler::startTime().toTime_t()), + QCoreApplication::organizationName(), + QCoreApplication::applicationName(), + QCoreApplication::applicationVersion(), + QtSystemExceptionHandler::plugins(), + QtSystemExceptionHandler::buildVersion(), + QCoreApplication::applicationFilePath() + }; + + qWarning() << "[execute crash handler]" << QtSystemExceptionHandler::crashHandlerPath(); + qWarning() << argumentList; + return !QProcess::execute(QtSystemExceptionHandler::crashHandlerPath(), argumentList); +} +#elif defined(Q_OS_WIN) +static bool exceptionHandlerCallback(const wchar_t* dump_path, + const wchar_t* minidump_id, + void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + bool succeeded) +{ + Q_UNUSED(assertion); + Q_UNUSED(exinfo); + Q_UNUSED(context); + + if (!succeeded) + return succeeded; + + const QString path = QString::fromWCharArray(dump_path, int(wcslen(dump_path))) + QLatin1String("/") + + QString::fromWCharArray(minidump_id, int(wcslen(minidump_id))) + QLatin1String(".dmp"); + + const QStringList argumentList = { + path, + QString::number(QtSystemExceptionHandler::startTime().toTime_t()), + QCoreApplication::organizationName(), + QCoreApplication::applicationName(), + QCoreApplication::applicationVersion(), + QtSystemExceptionHandler::plugins(), + QtSystemExceptionHandler::buildVersion(), + QCoreApplication::applicationFilePath() + }; + + qWarning() << "[execute crash handler]" << QtSystemExceptionHandler::crashHandlerPath(); + qWarning() << argumentList; + return !QProcess::execute(QtSystemExceptionHandler::crashHandlerPath(), argumentList); +} +#endif + +static QDir getAppTempDir() +{ + QDir temp_dir = QDir::tempPath(); + QString org_name = QCoreApplication::organizationName(); + QString app_name = QCoreApplication::applicationName(); + + if (!org_name.isEmpty() && !app_name.isEmpty()) + temp_dir = temp_dir.filePath(org_name + QLatin1String(".") + app_name); + else if (!org_name.isEmpty()) + temp_dir = temp_dir.filePath(org_name); + else if (!app_name.isEmpty()) + temp_dir = temp_dir.filePath(app_name); + + temp_dir.mkdir(QLatin1String(".")); + return temp_dir; +} + +static QDateTime s_startTime; +static QString s_plugins; +static QString s_buildVersion; +static QString s_crashHandlerPath; + +#if defined(Q_OS_LINUX) +QtSystemExceptionHandler::QtSystemExceptionHandler(const QString &libexecPath) + : exceptionHandler(new google_breakpad::ExceptionHandler( + google_breakpad::MinidumpDescriptor(QDir::tempPath().toStdString()), + NULL, + exceptionHandlerCallback, + NULL, + true, + -1)) +{ + init(libexecPath); +} +#elif defined(Q_OS_MACOS) +QtSystemExceptionHandler::QtSystemExceptionHandler(const QString &libexecPath) + : exceptionHandler(new google_breakpad::ExceptionHandler( + QDir::tempPath().toStdString(), + NULL, + exceptionHandlerCallback, + NULL, + true, + NULL)) +{ + init(libexecPath); +} +#elif defined(Q_OS_WIN) +QtSystemExceptionHandler::QtSystemExceptionHandler(const QString &libexecPath) + : exceptionHandler(new google_breakpad::ExceptionHandler( + getAppTempDir().absolutePath().toStdWString(), + NULL, + exceptionHandlerCallback, + NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL)) +{ + init(libexecPath); +} +#else +QtSystemExceptionHandler::QtSystemExceptionHandler(const QString & /*libexecPath*/) + : exceptionHandler(0) +{ + +} +#endif + +void QtSystemExceptionHandler::init(const QString &libexecPath) +{ + s_startTime = QDateTime::currentDateTime(); + + s_crashHandlerPath = libexecPath + QLatin1String("/QtCrashHandler"); + +#ifdef Q_OS_WIN + s_crashHandlerPath = libexecPath + QLatin1String("/QtCrashHandler.exe"); +#endif + +#ifdef Q_OS_MACOS + s_crashHandlerPath = libexecPath + QLatin1String("/QtCrashHandler"); +#endif +} + +QtSystemExceptionHandler::~QtSystemExceptionHandler() +{ +#ifdef ENABLE_QT_BREAKPAD + delete exceptionHandler; +#endif +} + +void QtSystemExceptionHandler::setPlugins(const QStringList &pluginNameList) +{ + s_plugins = QString::fromLatin1("{%1}").arg(pluginNameList.join(QLatin1String(","))); +} + +void QtSystemExceptionHandler::setBuildVersion(const QString &version) +{ + s_buildVersion = version; +} + +QString QtSystemExceptionHandler::buildVersion() +{ + return s_buildVersion; +} + +QString QtSystemExceptionHandler::plugins() +{ + return s_plugins; +} + +void QtSystemExceptionHandler::setCrashHandlerPath(const QString &crashHandlerPath) +{ + s_crashHandlerPath = crashHandlerPath; +} + +QString QtSystemExceptionHandler::crashHandlerPath() +{ + return s_crashHandlerPath; +} + +void QtSystemExceptionHandler::crash() +{ + int *a = (int*)0x42; + + fprintf(stdout, "Going to crash...\n"); + fprintf(stdout, "A = %d", *a); +} + +QDateTime QtSystemExceptionHandler::startTime() +{ + return s_startTime; +} diff --git a/breakpad/qtsystemexceptionhandler.h b/breakpad/qtsystemexceptionhandler.h new file mode 100644 index 000000000..2cc3067d7 --- /dev/null +++ b/breakpad/qtsystemexceptionhandler.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace google_breakpad { + class ExceptionHandler; +} + +class QtSystemExceptionHandler +{ +public: + QtSystemExceptionHandler(const QString &libexecPath); + ~QtSystemExceptionHandler(); + + static void crash(); + static void setPlugins(const QStringList &pluginNameList); + static void setBuildVersion(const QString &version); + static void setCrashHandlerPath(const QString &crashHandlerPath); + + static QString plugins(); + static QString buildVersion(); + static QString crashHandlerPath(); + + static QDateTime startTime(); + +protected: + void init(const QString &libexecPath); + +private: + google_breakpad::ExceptionHandler *exceptionHandler = nullptr; +}; diff --git a/qtcrashhandler/CMakeLists.txt b/qtcrashhandler/CMakeLists.txt new file mode 100644 index 000000000..96a39d463 --- /dev/null +++ b/qtcrashhandler/CMakeLists.txt @@ -0,0 +1,39 @@ +if(NOT WIN32) + return() +endif() + +set(SOURCES + detaildialog.cpp + dumpsender.cpp + main.cpp + mainwidget.cpp + mainwidget.ui +) + +include_directories(${CMAKE_BINARY_DIR}/common/BlockSettleUILib) + +add_executable(QtCrashHandler ${SOURCES}) + +target_link_libraries(QtCrashHandler + ${CRYPTO_LIB_NAME} + ${QT_LINUX_LIBS} + Qt5::Widgets + Qt5::Network + ${QT_LIBS} + ${OS_SPECIFIC_LIBS} + ${OPENSSL_LIBS} +) + +if(ENABLE_CRASH_HANDLER) + target_compile_definitions(QtCrashHandler PRIVATE -DENABLE_CRASH_HANDLER) +endif() + +set_target_properties(QtCrashHandler PROPERTIES AUTOUIC ON) + +add_executable(CleanUpSystray WIN32 CleanUpSystray.cpp) + +target_link_libraries(CleanUpSystray + ${OS_SPECIFIC_LIBS} + bufferoverflowU.lib + libcmt.lib +) diff --git a/qtcrashhandler/CleanUpSystray.cpp b/qtcrashhandler/CleanUpSystray.cpp new file mode 100644 index 000000000..a039911b8 --- /dev/null +++ b/qtcrashhandler/CleanUpSystray.cpp @@ -0,0 +1,35 @@ +// See: https://stackoverflow.com/a/56088800/262458 + +#include + +static void MoveMouseOverRect(RECT r, HWND w) +{ + for (LONG x = 0; x < r.right; x += 5) { + for (LONG y = 0; y < r.bottom; y += 5) { + SendMessage(w, WM_MOUSEMOVE, 0, (y << 16) + x); + } + } +} + +#define FW(x,y) FindWindowExW(x, NULL, y, L"") + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR pCmdLine, int nCmdShow) +{ + HWND hNotificationArea; + RECT r; + + // give crashed app time to close, so icon can be cleaned up + Sleep(2000); + + // main systray + GetClientRect(hNotificationArea = FindWindowExW(FW(FW(FW(NULL, L"Shell_TrayWnd"), L"TrayNotifyWnd"), L"SysPager"), NULL, L"ToolbarWindow32", NULL), &r); + + MoveMouseOverRect(r, hNotificationArea); + + // overflow area + GetClientRect(hNotificationArea = FindWindowExW(FW(NULL, L"NotifyIconOverflowWindow"), NULL, L"ToolbarWindow32", NULL), &r); + + MoveMouseOverRect(r, hNotificationArea); + + return 0; +} diff --git a/qtcrashhandler/detaildialog.cpp b/qtcrashhandler/detaildialog.cpp new file mode 100644 index 000000000..21c7e87c8 --- /dev/null +++ b/qtcrashhandler/detaildialog.cpp @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "detaildialog.h" + +#include +#include +#include + +DetailDialog::DetailDialog(QWidget *parent) : + QDialog(parent) +{ + resize(640, 480); + QVBoxLayout *verticalLayout = new QVBoxLayout(this); + textBrowser = new QTextBrowser(this); + verticalLayout->addWidget(textBrowser); + buttonBox = new QDialogButtonBox(this); + buttonBox->setOrientation(Qt::Horizontal); + buttonBox->setStandardButtons(QDialogButtonBox::Close); + + verticalLayout->addWidget(buttonBox); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +DetailDialog::~DetailDialog() +{ +} + +void DetailDialog::setText(const QString &text) +{ + textBrowser->setPlainText(text); +} diff --git a/qtcrashhandler/detaildialog.h b/qtcrashhandler/detaildialog.h new file mode 100644 index 000000000..9db319a4e --- /dev/null +++ b/qtcrashhandler/detaildialog.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +QT_BEGIN_NAMESPACE +class QTextBrowser; +class QDialogButtonBox; +QT_END_NAMESPACE + +class DetailDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DetailDialog(QWidget *parent = nullptr); + ~DetailDialog(); + + void setText(const QString &text); + +private: + QTextBrowser *textBrowser = nullptr; + QDialogButtonBox *buttonBox = nullptr; +}; diff --git a/qtcrashhandler/dumpsender.cpp b/qtcrashhandler/dumpsender.cpp new file mode 100644 index 000000000..6693305f3 --- /dev/null +++ b/qtcrashhandler/dumpsender.cpp @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "dumpsender.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static QString getApplicationArguments(int idx) +{ + if (QCoreApplication::arguments().size() > idx) + return QCoreApplication::arguments().at(idx); + + return QLatin1String(""); +} + +DumpSender::DumpSender(QUrl submitUrl, QObject *parent) : + m_submitUrl(submitUrl), QObject(parent), + m_httpMultiPart(QHttpMultiPart::FormDataType) +{ + const QString dumpPath = getApplicationArguments(1); + const QByteArray startupTime = getApplicationArguments(2).toLocal8Bit(); + const QByteArray organizationName = getApplicationArguments(3).toLocal8Bit(); + const QByteArray applicationName = getApplicationArguments(4).toLocal8Bit(); + QByteArray applicationVersion = getApplicationArguments(5).toLocal8Bit(); + const QByteArray plugins = getApplicationArguments(6).toLocal8Bit(); + // QByteArray ideRevision = getApplicationArguments(6).toLocal8Bit(); + m_applicationFilePath = getApplicationArguments(8); + + if (applicationVersion.isEmpty()) + applicationVersion = "1.0.0"; + + QFile dumpFile(dumpPath, this); + const bool isOpen = dumpFile.open(QIODevice::ReadOnly); + //Q_ASSERT(isOpen); + Q_UNUSED(isOpen); + + const QList > pairList = { + { "StartupTime", startupTime }, + { "Vendor", "Qt Project" }, + { "InstallTime", "0" }, + { "Add-ons", plugins }, + { "BuildID", "" }, + { "SecondsSinceLastCrash", "0" }, + { "OrganizationName", organizationName }, + { "ProductName", applicationName }, + { "URL", "" }, + { "Theme", "" }, + { "Version", applicationVersion }, + { "CrashTime", QByteArray::number(QDateTime::currentDateTime().toTime_t()) }, + { "Throttleable", "0" } + }; + + const QByteArray boundary = m_httpMultiPart.boundary(); + m_formData.append("--" + boundary + "\r\n"); + for (const auto &pair : pairList) { + m_formData.append("Content-Disposition: form-data; name=\"" + pair.first + "\"\r\n\r\n"); + m_formData.append(pair.second + "\r\n"); + m_formData.append("--" + boundary + "\r\n"); + } + + + QByteArray dumpArray = dumpFile.readAll(); + m_formData.append("Content-Type: application/octet-stream\r\n"); + m_formData.append("Content-Disposition: form-data; name=\"upload_file_minidump\"; filename=\"" + + QFileInfo(dumpPath).baseName().toUtf8() + "\r\n"); + m_formData.append("Content-Transfer-Encoding: binary\r\n\r\n"); + m_formData.append(dumpArray); + + m_formData.append("--" + boundary + "--\r\n"); + + for (const auto &pair : pairList) { + QHttpPart httpPart; + httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"" + pair.first + "\""); + httpPart.setBody(pair.second); + m_httpMultiPart.append(httpPart); + } + + QHttpPart dumpPart; + dumpPart.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/octet-stream")); + dumpPart.setHeader(QNetworkRequest::ContentDispositionHeader, + "form-data; name=\"upload_file_minidump\"; filename=\"" + + QFileInfo(dumpPath).baseName().toUtf8() + "\""); + dumpPart.setRawHeader("Content-Transfer-Encoding:", "binary"); + dumpPart.setBody(dumpArray); + m_httpMultiPart.append(dumpPart); +} + +void DumpSender::sendDumpAndQuit() +{ + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + + QNetworkRequest request(m_submitUrl); + + const QByteArray boundary = m_httpMultiPart.boundary(); + request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + boundary); + + QList> pairList; + + if (!m_emailAddress.isEmpty()) + pairList.append({ "Email", m_emailAddress.toLocal8Bit() }); + + if (!m_commentText.isEmpty()) + pairList.append({ "Comments", m_commentText.toLocal8Bit() }); + + for (const auto &pair : pairList) { + m_formData.append("Content-Disposition: form-data; name=\"" + pair.first + "\"\r\n\r\n"); + m_formData.append(pair.second + "\r\n"); + m_formData.append("--" + boundary + "\r\n"); + } + + for (const auto &pair : pairList) { + QHttpPart httpPart; + httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"" + pair.first + "\""); + httpPart.setBody(pair.second); + m_httpMultiPart.append(httpPart); + } + + QNetworkReply *reply = manager->post(request, &m_httpMultiPart); + + m_httpMultiPart.setParent(reply); + + connect(reply, &QNetworkReply::uploadProgress, this, &DumpSender::uploadProgress); + connect(reply, &QNetworkReply::finished, QCoreApplication::instance(), &QCoreApplication::quit); + connect(reply, + static_cast(&QNetworkReply::error), + QCoreApplication::instance(), &QCoreApplication::quit); +} + +void DumpSender::restartCrashedApplicationAndSendDump() +{ + QProcess::startDetached(m_applicationFilePath); + sendDumpAndQuit(); +} + +void DumpSender::restartCrashedApplication() +{ + QProcess::startDetached(m_applicationFilePath); + QCoreApplication::quit(); +} + +void DumpSender::setEmailAddress(const QString &email) +{ + m_emailAddress = email; +} + +void DumpSender::setCommentText(const QString &comment) +{ + m_commentText = comment; +} + +int DumpSender::dumperSize() const +{ + return m_formData.size(); +} diff --git a/qtcrashhandler/dumpsender.h b/qtcrashhandler/dumpsender.h new file mode 100644 index 000000000..7d3e40a5f --- /dev/null +++ b/qtcrashhandler/dumpsender.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +class DumpSender : public QObject +{ + Q_OBJECT + +public: + explicit DumpSender(QUrl submitUrl, QObject *parent = nullptr); + + int dumperSize() const; + + void sendDumpAndQuit(); + void restartCrashedApplication(); + void restartCrashedApplicationAndSendDump(); + void setEmailAddress(const QString &email); + void setCommentText(const QString &comment); + +signals: + void uploadProgress(qint64 bytesSent, qint64 bytesTotal); + +private: + QUrl m_submitUrl; + QHttpMultiPart m_httpMultiPart; + QByteArray m_formData; + QString m_applicationFilePath; + QString m_emailAddress; + QString m_commentText; +}; diff --git a/qtcrashhandler/main.cpp b/qtcrashhandler/main.cpp new file mode 100644 index 000000000..2fef30cb5 --- /dev/null +++ b/qtcrashhandler/main.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "mainwidget.h" +#include "dumpsender.h" + +#include +#include +#include +#include +#include +#include + +Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); + +int main(int argc, char *argv[]) +{ + QApplication application(argc, argv); + +#ifdef ENABLE_CRASH_HANDLER + if (application.arguments().count() > 1) { + const QString dumpPath = QApplication::arguments().at(1); + if (!QFileInfo(dumpPath).exists()) + qWarning("dumpPath not exist"); + } + else { + qWarning("no dumpPath specified"); + } + + QNetworkProxyFactory::setUseSystemConfiguration(true); + + QHostInfo hostInfo = QHostInfo::fromName(QLatin1String("crashes.qt.io")); + + //if (hostInfo.error() != QHostInfo::NoError) + // return 0; + + QUrl submitUrl(QLatin1String("http://www.blocksettle.com/crash_report")); + DumpSender dumpSender(submitUrl); + + MainWidget mainWindow; + + mainWindow.setProgressbarMaximum(dumpSender.dumperSize()); + + QObject::connect(&mainWindow, &MainWidget::restartCrashedApplication, + &dumpSender, &DumpSender::restartCrashedApplication); + QObject::connect(&mainWindow, &MainWidget::restartCrashedApplicationAndSendDump, + &dumpSender, &DumpSender::restartCrashedApplicationAndSendDump); + QObject::connect(&mainWindow, &MainWidget::sendDump, + &dumpSender, &DumpSender::sendDumpAndQuit); + QObject::connect(&mainWindow, &MainWidget::commentChanged, + &dumpSender, &DumpSender::setCommentText); + QObject::connect(&mainWindow, &MainWidget::emailAdressChanged, + &dumpSender, &DumpSender::setEmailAddress); + QObject::connect(&dumpSender, &DumpSender::uploadProgress, + &mainWindow, &MainWidget::updateProgressBar); + + mainWindow.show(); + + int exit_code = application.exec(); +#else + int exit_code = 0; +#endif // ENABLE_CRASH_HANDLER + +#ifdef Q_OS_WIN + QProcess::startDetached(application.applicationDirPath() + QLatin1String("/CleanUpSystray.exe")); +#endif + + return exit_code; +} diff --git a/qtcrashhandler/mainwidget.cpp b/qtcrashhandler/mainwidget.cpp new file mode 100644 index 000000000..bf9f798d3 --- /dev/null +++ b/qtcrashhandler/mainwidget.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "mainwidget.h" +#include "ui_mainwidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static QString getApplicationArguments(int idx) +{ + if (QCoreApplication::arguments().size() > idx) + return QCoreApplication::arguments().at(idx); + + return QLatin1String(""); +} + +MainWidget::MainWidget(QWidget *parent) : + QWidget(parent), + ui(new Ui::MainWidget) +{ + ui->setupUi(this); + + ui->mainWidgetTopLabel->setText(tr("%1 %2 has crashed").arg(getApplicationArguments(3)).arg(getApplicationArguments(4))); + + connect(ui->restartButton, &QAbstractButton::clicked, this, &MainWidget::restartApplication); + connect(ui->quitButton, &QAbstractButton::clicked, this, &MainWidget::quitApplication); + connect(ui->detailButton, &QAbstractButton::clicked, this, &MainWidget::showDetails); + connect(ui->commentTextEdit, &QTextEdit::textChanged, this, &MainWidget::commentIsProvided); + connect(ui->emailLineEdit, &QLineEdit::textEdited, this, &MainWidget::emailAdressChanged); +} + +MainWidget::~MainWidget() +{ + delete ui; +} + +void MainWidget::setProgressbarMaximum(int maximum) +{ + ui->progressBar->setMaximum(maximum); +} + +void MainWidget::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + if (e->type() == QEvent::LanguageChange) + ui->retranslateUi(this); +} + +void MainWidget::updateProgressBar(qint64 progressCount, qint64 fullCount) +{ + ui->progressBar->setValue(static_cast(progressCount)); + ui->progressBar->setMaximum(static_cast(fullCount)); +} + +void MainWidget::showError(QNetworkReply::NetworkError error) +{ + QNetworkReply *reply = qobject_cast(sender()); + if (error != QNetworkReply::NoError && reply) { + ui->commentTextEdit->setReadOnly(true); + ui->commentTextEdit->setPlainText(reply->errorString()); + } +} + +void MainWidget::restartApplication() +{ + if (ui->sendDumpCheckBox->isChecked()) + emit restartCrashedApplicationAndSendDump(); + else + emit restartCrashedApplication(); +} + +void MainWidget::quitApplication() +{ + ui->quitButton->setEnabled(false); + if (ui->sendDumpCheckBox->isChecked()) + emit sendDump(); + else + QCoreApplication::quit(); +} + +void MainWidget::commentIsProvided() +{ + m_commentIsProvided = true; + emit commentChanged(ui->commentTextEdit->toPlainText()); +} + +void MainWidget::showDetails() +{ + if (m_detailDialog.isNull()) { + m_detailDialog = new DetailDialog(this); + + QString detailText; + + detailText.append(tr("We specifically send the following information:\n\n")); + + QString dumpPath = getApplicationArguments(1); + QString startupTime = getApplicationArguments(2); + QString organizationName = getApplicationArguments(3); + QString applicationName = getApplicationArguments(4); + QString applicationVersion = getApplicationArguments(5); + QString plugins = getApplicationArguments(6); + QString ideRevision = getApplicationArguments(7); + + detailText.append(QString::fromLatin1("StartupTime: %1\n").arg(startupTime)); + detailText.append(QString::fromLatin1("Organization: %1\n").arg(organizationName)); +// detailText.append(QString::fromLatin1("InstallTime: %1\n").arg(QLatin1String("0"))); +// detailText.append(QString::fromLatin1("Add-ons: %1\n").arg(plugins)); +// detailText.append(QString::fromLatin1("BuildID: %1\n").arg(QLatin1String("0"))); +// detailText.append(QString::fromLatin1("SecondsSinceLastCrash: %1\n").arg(QLatin1String("0"))); + detailText.append(QString::fromLatin1("ProductName: %1\n").arg(applicationName)); +// detailText.append(QString::fromLatin1("URL: %1\n").arg(QLatin1String(""))); +// detailText.append(QString::fromLatin1("Theme: %1\n").arg(QLatin1String(""))); + detailText.append(QString::fromLatin1("Version: %1\n").arg(applicationVersion)); + detailText.append(QString::fromLatin1("CrashTime: %1\n").arg(QString::number(QDateTime::currentDateTime().toTime_t()))); + + if (!ui->emailLineEdit->text().isEmpty()) + detailText.append(tr("Email: %1\n").arg(ui->emailLineEdit->text())); + + if (m_commentIsProvided) + detailText.append(tr("Comments: %1\n").arg(ui->commentTextEdit->toPlainText())); + + detailText.append( + tr("In addition, we send a Microsoft Minidump file, which contains information " + "about this computer, such as the operating system and CPU, and most " + "importantly, it contains the stacktrace, which is an internal structure that " + "shows where the program crashed. This information will help us to identify " + "the cause of the crash and to fix it.")); + + m_detailDialog.data()->setText(detailText); + } + if (m_detailDialog->isVisible()) + m_detailDialog->showNormal(); + else + m_detailDialog->show(); +} diff --git a/qtcrashhandler/mainwidget.h b/qtcrashhandler/mainwidget.h new file mode 100644 index 000000000..4e3b9880c --- /dev/null +++ b/qtcrashhandler/mainwidget.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "detaildialog.h" + +#include +#include +#include +#include + +namespace Ui { class MainWidget; } + +class MainWidget : public QWidget +{ + Q_OBJECT + +public: + explicit MainWidget(QWidget *parent = nullptr); + ~MainWidget(); + + void setProgressbarMaximum(int maximum); + void updateProgressBar(qint64 progressCount, qint64 fullCount); + +signals: + void restartCrashedApplication(); + void sendDump(); + void restartCrashedApplicationAndSendDump(); + void emailAdressChanged(const QString &email); + void commentChanged(const QString &comment); + +protected: + void changeEvent(QEvent *e); + +private: + void restartApplication(); + void quitApplication(); + void showError(QNetworkReply::NetworkError error); + void showDetails(); + void commentIsProvided(); + +private: + Ui::MainWidget *ui; + + QPointer m_detailDialog; + bool m_commentIsProvided = false; +}; diff --git a/qtcrashhandler/mainwidget.ui b/qtcrashhandler/mainwidget.ui new file mode 100644 index 000000000..8c49c80db --- /dev/null +++ b/qtcrashhandler/mainwidget.ui @@ -0,0 +1,140 @@ + + + MainWidget + + + + 0 + 0 + 422 + 510 + + + + Crash Handler + + + + + + + 20 + + + + BlockSettle Terminal has crashed + + + false + + + + + + + You can send us a crash report in order to help us diagnose and fix the problem. + + + true + + + + + + + + + Email: + + + + + + + Enter here your email (optional) + + + + + + + + + Tell BlockSettle about this crash so they can fix it + + + true + + + + + + + Details + + + + + + + true + + + Please describe what you did before it crashed + + + + + + + Your crash report will be submitted before you quit or restart. + + + + + + + 0 + + + %v/%m Bytes + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Restart + + + + + + + Quit + + + + + + + + + + +