summaryrefslogtreecommitdiffstats
path: root/src/shared
diff options
context:
space:
mode:
authorMahmoud Badri <mahmoud.badri@qt.io>2018-11-09 14:41:10 +0200
committerMahmoud Badri <mahmoud.badri@qt.io>2018-11-15 12:56:07 +0000
commit9d4afff6f57142cfa43b5fce1222427973e3a574 (patch)
tree997c04ae8e953100b1dff1f129fbce5d92d2cf63 /src/shared
parent4e97e4edc636b306eb1009cc5c6c189d78ae7774 (diff)
Integrate Google breakpad with Qt 3D Studio
When a crash happens, a minidump file is generated and a friendly popup appears that collects info and send it along the crash minidump file to the server. Sentry.io is used as the crash server. Breakpad is referenced via an environmental variable (BREAKPAD_SOURCE_DIR) Task-number: QT3DS-2522 Task-number: QT3DS-2523 Change-Id: I1fd53b9bfea4512ef74abc2a43834ad17224f561 Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Tomi Korpipää <tomi.korpipaa@qt.io>
Diffstat (limited to 'src/shared')
-rw-r--r--src/shared/qt-breakpad/qt-breakpad.pro3
-rw-r--r--src/shared/qt-breakpad/qtbreakpad.pri60
-rw-r--r--src/shared/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.cpp224
-rw-r--r--src/shared/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.h59
-rw-r--r--src/shared/qt-breakpad/qtcrashhandler.pri19
-rw-r--r--src/shared/qt-breakpad/qtcrashhandler/detaildialog.cpp58
-rw-r--r--src/shared/qt-breakpad/qtcrashhandler/detaildialog.h51
-rw-r--r--src/shared/qt-breakpad/qtcrashhandler/dumpsender.cpp180
-rw-r--r--src/shared/qt-breakpad/qtcrashhandler/dumpsender.h58
-rw-r--r--src/shared/qt-breakpad/qtcrashhandler/main.cpp68
-rw-r--r--src/shared/qt-breakpad/qtcrashhandler/mainwidget.cpp153
-rw-r--r--src/shared/qt-breakpad/qtcrashhandler/mainwidget.h72
-rw-r--r--src/shared/qt-breakpad/qtcrashhandler/mainwidget.ui143
-rw-r--r--src/shared/qt-breakpad/qtcrashhandler/qtcrashhandler.pro23
-rw-r--r--src/shared/shared.pro6
15 files changed, 1177 insertions, 0 deletions
diff --git a/src/shared/qt-breakpad/qt-breakpad.pro b/src/shared/qt-breakpad/qt-breakpad.pro
new file mode 100644
index 00000000..b012faff
--- /dev/null
+++ b/src/shared/qt-breakpad/qt-breakpad.pro
@@ -0,0 +1,3 @@
+TEMPLATE = subdirs
+CONFIG += ordered
+SUBDIRS += qtcrashhandler
diff --git a/src/shared/qt-breakpad/qtbreakpad.pri b/src/shared/qt-breakpad/qtbreakpad.pri
new file mode 100644
index 00000000..e62dec64
--- /dev/null
+++ b/src/shared/qt-breakpad/qtbreakpad.pri
@@ -0,0 +1,60 @@
+HEADERS += $$PWD/qtbreakpad/qtsystemexceptionhandler.h
+SOURCES += $$PWD/qtbreakpad/qtsystemexceptionhandler.cpp
+
+DEFINES += ENABLE_QT_BREAKPAD
+
+INCLUDEPATH += \
+ $$(BREAKPAD_SOURCE_DIR)/src \
+ $$PWD/qtbreakpad
+
+SOURCES += \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/string_conversion.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/convert_UTF.c \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/md5.cc
+
+linux:SOURCES += \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/minidump_file_writer.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/linux/log/log.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/linux/handler/exception_handler.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/linux/handler/minidump_descriptor.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/linux/guid_creator.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/linux/dump_writer_common/thread_info.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/linux/dump_writer_common/ucontext_reader.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/linux/minidump_writer/linux_dumper.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/linux/minidump_writer/minidump_writer.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/linux/minidump_writer/linux_ptrace_dumper.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/linux/microdump_writer/microdump_writer.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/linux/file_id.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/linux/elfutils.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/linux/linux_libc_support.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/linux/memory_mapped_file.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/linux/safe_readlink.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/linux/crash_generation/crash_generation_client.cc
+
+win32:SOURCES += \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/windows/guid_string.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/windows/handler/exception_handler.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/windows/crash_generation/minidump_generator.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/windows/crash_generation/client_info.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/windows/crash_generation/crash_generation_client.cc
+
+macos {
+ SOURCES += \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/minidump_file_writer.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/mac/crash_generation/crash_generation_client.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/mac/handler/exception_handler.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/mac/handler/minidump_generator.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/mac/handler/breakpad_nlist_64.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/mac/handler/dynamic_images.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/client/mac/handler/protected_memory_allocator.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/mac/bootstrap_compat.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/mac/file_id.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/mac/macho_id.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/mac/macho_reader.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/mac/macho_utilities.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/mac/macho_walker.cc \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/mac/string_utilities.cc
+ OBJECTIVE_SOURCES += \
+ $$(BREAKPAD_SOURCE_DIR)/src/common/mac/MachIPC.mm
+ LIBS += -framework Foundation
+}
diff --git a/src/shared/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.cpp b/src/shared/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.cpp
new file mode 100644
index 00000000..6f63ea4f
--- /dev/null
+++ b/src/shared/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.cpp
@@ -0,0 +1,224 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtsystemexceptionhandler.h"
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QDir>
+#include <QProcess>
+#include "StudioPreferences.h"
+
+static QDateTime s_startTime;
+static QString s_plugins;
+static QString s_buildVersion;
+static QString s_crashHandlerPath;
+
+#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::applicationName(),
+ QCoreApplication::applicationVersion(),
+ QtSystemExceptionHandler::plugins(),
+ QtSystemExceptionHandler::buildVersion(),
+ QCoreApplication::applicationFilePath()
+ };
+
+ 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::applicationName(),
+ QCoreApplication::applicationVersion(),
+ QtSystemExceptionHandler::plugins(),
+ QtSystemExceptionHandler::buildVersion(),
+ QCoreApplication::applicationFilePath()
+ };
+
+ 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 false;
+
+ const QString path = QString::fromWCharArray(dump_path, int(wcslen(dump_path)))
+ + QLatin1Char('/') + QString::fromWCharArray(minidump_id, int(wcslen(minidump_id)))
+ + QStringLiteral(".dmp");
+ const QStringList argumentList {
+ path,
+ QString::number(QtSystemExceptionHandler::startTime().toTime_t()),
+ QCoreApplication::applicationName(),
+ QCoreApplication::applicationVersion(),
+ QtSystemExceptionHandler::plugins(),
+ QtSystemExceptionHandler::buildVersion(),
+ QCoreApplication::applicationFilePath()
+ };
+
+ return !QProcess::execute(QtSystemExceptionHandler::crashHandlerPath(), argumentList);
+}
+#endif
+
+#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(
+ QDir::tempPath().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;
+ s_crashHandlerPath += QLatin1String("/qtcrashhandler");
+#ifdef Q_OS_WIN
+ s_crashHandlerPath += QLatin1String(".exe");
+#endif
+}
+
+QtSystemExceptionHandler::~QtSystemExceptionHandler()
+{
+ delete exceptionHandler;
+}
+
+void QtSystemExceptionHandler::setPlugins(const QStringList &pluginNameList)
+{
+ s_plugins = QString("{%1}").arg(pluginNameList.join(","));
+}
+
+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/src/shared/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.h b/src/shared/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.h
new file mode 100644
index 00000000..b750579f
--- /dev/null
+++ b/src/shared/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QDateTime>
+
+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/src/shared/qt-breakpad/qtcrashhandler.pri b/src/shared/qt-breakpad/qtcrashhandler.pri
new file mode 100644
index 00000000..cc1c8677
--- /dev/null
+++ b/src/shared/qt-breakpad/qtcrashhandler.pri
@@ -0,0 +1,19 @@
+QT += network
+
+INCLUDEPATH += \
+ $$(BREAKPAD_SOURCE_DIR)/src \
+ $$PWD/qtcrashhandler
+
+SOURCES += \
+ $$PWD/qtcrashhandler/main.cpp \
+ $$PWD/qtcrashhandler/mainwidget.cpp \
+ $$PWD/qtcrashhandler/detaildialog.cpp \
+ $$PWD/qtcrashhandler/dumpsender.cpp
+
+HEADERS += \
+ $$PWD/qtcrashhandler/mainwidget.h \
+ $$PWD/qtcrashhandler/detaildialog.h \
+ $$PWD/qtcrashhandler/dumpsender.h
+
+FORMS += \
+ $$PWD/qtcrashhandler/mainwidget.ui
diff --git a/src/shared/qt-breakpad/qtcrashhandler/detaildialog.cpp b/src/shared/qt-breakpad/qtcrashhandler/detaildialog.cpp
new file mode 100644
index 00000000..476a491f
--- /dev/null
+++ b/src/shared/qt-breakpad/qtcrashhandler/detaildialog.cpp
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "detaildialog.h"
+
+#include <QDialogButtonBox>
+#include <QTextBrowser>
+#include <QVBoxLayout>
+
+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/src/shared/qt-breakpad/qtcrashhandler/detaildialog.h b/src/shared/qt-breakpad/qtcrashhandler/detaildialog.h
new file mode 100644
index 00000000..416151de
--- /dev/null
+++ b/src/shared/qt-breakpad/qtcrashhandler/detaildialog.h
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QDialog>
+
+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/src/shared/qt-breakpad/qtcrashhandler/dumpsender.cpp b/src/shared/qt-breakpad/qtcrashhandler/dumpsender.cpp
new file mode 100644
index 00000000..f9f65906
--- /dev/null
+++ b/src/shared/qt-breakpad/qtcrashhandler/dumpsender.cpp
@@ -0,0 +1,180 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "dumpsender.h"
+
+#include <QCoreApplication>
+#include <QDateTime>
+#include <QDebug>
+#include <QFile>
+#include <QFileInfo>
+#include <QHttpMultiPart>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QPair>
+#include <QProcess>
+#include <QStringList>
+#include <QTemporaryFile>
+#include <QUrl>
+
+DumpSender::DumpSender(QObject *parent) :
+ QObject(parent),
+ m_httpMultiPart(QHttpMultiPart::FormDataType)
+{
+ const QString dumpPath = QCoreApplication::arguments().at(1);
+ const QByteArray startupTime = QCoreApplication::arguments().at(2).toLocal8Bit();
+ const QByteArray applicationName = QCoreApplication::arguments().at(3).toLocal8Bit();
+ QByteArray applicationVersion = QCoreApplication::arguments().at(4).toLocal8Bit();
+ const QByteArray plugins = QCoreApplication::arguments().at(5).toLocal8Bit();
+ const QByteArray revision = QCoreApplication::arguments().at(6).toLocal8Bit();
+ m_applicationFilePath = QCoreApplication::arguments().at(7);
+
+ 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<QPair<QByteArray, QByteArray> > pairList {
+ { "ProductName", applicationName },
+ { "Version", applicationVersion },
+ { "Revision", revision },
+ { "Vendor", "Qt Project" },
+ { "StartupTime", startupTime },
+ { "Plugins", plugins },
+ { "CrashTime", QByteArray::number(QDateTime::currentDateTime().toTime_t()) }
+ };
+
+ 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, "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);
+
+ const QUrl SENTRY_URL = QUrl("https://sentry.io/api/1312159/minidump/"
+ "?sentry_key=233b24a2d9d549aaacde112de834ee85");
+
+ QNetworkRequest request(SENTRY_URL);
+
+ const QByteArray boundary = m_httpMultiPart.boundary();
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary="
+ + boundary);
+
+ QList<QPair<QByteArray, QByteArray>> 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<void (QNetworkReply::*)(QNetworkReply::NetworkError)>
+ (&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/src/shared/qt-breakpad/qtcrashhandler/dumpsender.h b/src/shared/qt-breakpad/qtcrashhandler/dumpsender.h
new file mode 100644
index 00000000..632bdfc8
--- /dev/null
+++ b/src/shared/qt-breakpad/qtcrashhandler/dumpsender.h
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QHttpMultiPart>
+#include <QObject>
+
+class DumpSender : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit DumpSender(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:
+ QHttpMultiPart m_httpMultiPart;
+ QByteArray m_formData;
+ QString m_applicationFilePath;
+ QString m_emailAddress;
+ QString m_commentText;
+};
diff --git a/src/shared/qt-breakpad/qtcrashhandler/main.cpp b/src/shared/qt-breakpad/qtcrashhandler/main.cpp
new file mode 100644
index 00000000..5f48e82a
--- /dev/null
+++ b/src/shared/qt-breakpad/qtcrashhandler/main.cpp
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtWidgets/qapplication.h>
+#include <QtCore/qfileinfo.h>
+#include <QtNetwork/qnetworkproxy.h>
+
+#include "mainwidget.h"
+#include "dumpsender.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication application(argc, argv);
+
+ if (application.arguments().count() <= 1)
+ return 0;
+
+ const QString dumpPath = QApplication::arguments().at(1);
+ if (!QFileInfo(dumpPath).exists())
+ return 0;
+
+ QNetworkProxyFactory::setUseSystemConfiguration(true);
+
+ DumpSender dumpSender;
+ 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();
+ return application.exec();
+}
diff --git a/src/shared/qt-breakpad/qtcrashhandler/mainwidget.cpp b/src/shared/qt-breakpad/qtcrashhandler/mainwidget.cpp
new file mode 100644
index 00000000..8346b5bb
--- /dev/null
+++ b/src/shared/qt-breakpad/qtcrashhandler/mainwidget.cpp
@@ -0,0 +1,153 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mainwidget.h"
+#include "ui_mainwidget.h"
+
+#include <QtWidgets/qapplication.h>
+#include "QtCore/qdatetime.h"
+#include "QtCore/qfile.h"
+#include <QtCore/qfileinfo.h>
+#include <QtNetwork/qnetworkreply.h>
+
+MainWidget::MainWidget(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::MainWidget)
+{
+ ui->setupUi(this);
+
+ 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<int>(progressCount));
+ ui->progressBar->setMaximum(static_cast<int>(fullCount));
+}
+
+void MainWidget::showError(QNetworkReply::NetworkError error)
+{
+ QNetworkReply *reply = qobject_cast<QNetworkReply*>(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 = QApplication::arguments().at(1);
+ QString startupTime = QApplication::arguments().at(2);
+ QString applicationName = QApplication::arguments().at(3);
+ QString applicationVersion = QApplication::arguments().at(4);
+ QString plugins = QApplication::arguments().at(5);
+ QString ideRevision = QApplication::arguments().at(6);
+
+ detailText.append(QStringLiteral("StartupTime: %1\n").arg(startupTime));
+ detailText.append(QStringLiteral("Vendor: %1\n").arg(QLatin1String("Qt Project")));
+ detailText.append(QStringLiteral("InstallTime: %1\n").arg('0'));
+ detailText.append(QStringLiteral("Add-ons: %1\n").arg(plugins));
+ detailText.append(QStringLiteral("BuildID: %1\n").arg('0'));
+ detailText.append(QStringLiteral("SecondsSinceLastCrash: %1\n").arg('0'));
+ detailText.append(QStringLiteral("ProductName: %1\n").arg(applicationName));
+ detailText.append(QStringLiteral("URL: %1\n").arg(""));
+ detailText.append(QStringLiteral("Theme: %1\n").arg(""));
+ detailText.append(QStringLiteral("Version: %1\n").arg(applicationVersion));
+ detailText.append(QStringLiteral("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 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/src/shared/qt-breakpad/qtcrashhandler/mainwidget.h b/src/shared/qt-breakpad/qtcrashhandler/mainwidget.h
new file mode 100644
index 00000000..4cb00a38
--- /dev/null
+++ b/src/shared/qt-breakpad/qtcrashhandler/mainwidget.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include "detaildialog.h"
+
+#include <QtNetwork/qnetworkreply.h>
+#include <QtWidgets/qwidget.h>
+#include <QtCore/qpointer.h>
+
+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<DetailDialog> m_detailDialog;
+ bool m_commentIsProvided = false;
+};
diff --git a/src/shared/qt-breakpad/qtcrashhandler/mainwidget.ui b/src/shared/qt-breakpad/qtcrashhandler/mainwidget.ui
new file mode 100644
index 00000000..a4250325
--- /dev/null
+++ b/src/shared/qt-breakpad/qtcrashhandler/mainwidget.ui
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWidget</class>
+ <widget class="QWidget" name="MainWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>422</width>
+ <height>510</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Crash Handler</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="mainWidgetTopLabel">
+ <property name="font">
+ <font>
+ <pointsize>20</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>Qt 3D Studio has crashed</string>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>You can send us a crash report in order to help us diagnose and fix the problem.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Email:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="emailLineEdit">
+ <property name="placeholderText">
+ <string>Enter here your email (optional)</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="sendDumpCheckBox">
+ <property name="text">
+ <string>Tell The Qt Company about this crash so they can fix it</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="detailButton">
+ <property name="text">
+ <string>Details</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextEdit" name="commentTextEdit">
+ <property name="overwriteMode">
+ <bool>true</bool>
+ </property>
+ <property name="placeholderText">
+ <string>Please describe what you did before it crashed (comments are publicly visible)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Your crash report will be submitted before you quit or restart.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QProgressBar" name="progressBar">
+ <property name="value">
+ <number>0</number>
+ </property>
+ <property name="format">
+ <string>%v/%m Bytes</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="restartButton">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Restart</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="quitButton">
+ <property name="text">
+ <string>Quit</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/shared/qt-breakpad/qtcrashhandler/qtcrashhandler.pro b/src/shared/qt-breakpad/qtcrashhandler/qtcrashhandler.pro
new file mode 100644
index 00000000..20d64211
--- /dev/null
+++ b/src/shared/qt-breakpad/qtcrashhandler/qtcrashhandler.pro
@@ -0,0 +1,23 @@
+TARGET = qtcrashhandler
+QT += network widgets
+TEMPLATE = app
+
+INCLUDEPATH += \
+ $$(BREAKPAD_SOURCE_DIR)/src \
+ qtcrashhandler
+
+SOURCES += \
+ main.cpp \
+ mainwidget.cpp \
+ detaildialog.cpp \
+ dumpsender.cpp
+
+HEADERS += \
+ mainwidget.h \
+ detaildialog.h \
+ dumpsender.h
+
+FORMS += \
+ mainwidget.ui
+
+DESTDIR = ../../../../bin
diff --git a/src/shared/shared.pro b/src/shared/shared.pro
new file mode 100644
index 00000000..eba654e5
--- /dev/null
+++ b/src/shared/shared.pro
@@ -0,0 +1,6 @@
+TEMPLATE = subdirs
+
+exists ($$(BREAKPAD_SOURCE_DIR)) {
+ CONFIG += ordered
+ SUBDIRS += qt-breakpad
+}