mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
feat: Add crash recovery on Windows (#5012)
This commit is contained in:
parent
2cb965d352
commit
25add89b14
29 changed files with 563 additions and 258 deletions
|
@ -29,6 +29,7 @@ Checks: "-*,
|
||||||
-readability-function-cognitive-complexity,
|
-readability-function-cognitive-complexity,
|
||||||
-bugprone-easily-swappable-parameters,
|
-bugprone-easily-swappable-parameters,
|
||||||
-cert-err58-cpp,
|
-cert-err58-cpp,
|
||||||
|
-modernize-avoid-c-arrays
|
||||||
"
|
"
|
||||||
CheckOptions:
|
CheckOptions:
|
||||||
- key: readability-identifier-naming.ClassCase
|
- key: readability-identifier-naming.ClassCase
|
||||||
|
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -230,9 +230,9 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
cd build
|
cd build
|
||||||
set cl=/MP
|
set cl=/MP
|
||||||
nmake /S /NOLOGO crashpad_handler
|
nmake /S /NOLOGO chatterino-crash-handler
|
||||||
mkdir Chatterino2/crashpad
|
mkdir Chatterino2/crashpad
|
||||||
cp bin/crashpad/crashpad_handler.exe Chatterino2/crashpad/crashpad_handler.exe
|
cp bin/crashpad/crashpad-handler.exe Chatterino2/crashpad/crashpad-handler.exe
|
||||||
7z a bin/chatterino-Qt-${{ matrix.qt-version }}.pdb.7z bin/chatterino.pdb
|
7z a bin/chatterino-Qt-${{ matrix.qt-version }}.pdb.7z bin/chatterino.pdb
|
||||||
|
|
||||||
- name: Prepare build dir (windows)
|
- name: Prepare build dir (windows)
|
||||||
|
|
2
.github/workflows/clang-tidy.yml
vendored
2
.github/workflows/clang-tidy.yml
vendored
|
@ -124,7 +124,7 @@ jobs:
|
||||||
build_dir: build-clang-tidy
|
build_dir: build-clang-tidy
|
||||||
config_file: ".clang-tidy"
|
config_file: ".clang-tidy"
|
||||||
split_workflow: true
|
split_workflow: true
|
||||||
exclude: "lib/*"
|
exclude: "lib/*,tools/crash-handler/*"
|
||||||
cmake_command: >-
|
cmake_command: >-
|
||||||
cmake -S. -Bbuild-clang-tidy
|
cmake -S. -Bbuild-clang-tidy
|
||||||
-DCMAKE_BUILD_TYPE=Release
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
|
|
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -38,6 +38,6 @@
|
||||||
[submodule "lib/lua/src"]
|
[submodule "lib/lua/src"]
|
||||||
path = lib/lua/src
|
path = lib/lua/src
|
||||||
url = https://github.com/lua/lua
|
url = https://github.com/lua/lua
|
||||||
[submodule "lib/crashpad"]
|
[submodule "tools/crash-handler"]
|
||||||
path = lib/crashpad
|
path = tools/crash-handler
|
||||||
url = https://github.com/getsentry/crashpad
|
url = https://github.com/Chatterino/crash-handler
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
- Minor: Add `--safe-mode` command line option that can be used for troubleshooting when Chatterino is misbehaving or is misconfigured. It disables hiding the settings button & prevents plugins from loading. (#4985)
|
- Minor: Add `--safe-mode` command line option that can be used for troubleshooting when Chatterino is misbehaving or is misconfigured. It disables hiding the settings button & prevents plugins from loading. (#4985)
|
||||||
- Minor: Updated the flatpakref link included with nightly builds to point to up-to-date flathub-beta builds. (#5008)
|
- Minor: Updated the flatpakref link included with nightly builds to point to up-to-date flathub-beta builds. (#5008)
|
||||||
- Minor: Add a new completion API for experimental plugins feature. (#5000)
|
- Minor: Add a new completion API for experimental plugins feature. (#5000)
|
||||||
|
- Minor: Re-enabled _Restart on crash_ option on Windows. (#5012)
|
||||||
- Bugfix: Fixed an issue where certain emojis did not send to Twitch chat correctly. (#4840)
|
- Bugfix: Fixed an issue where certain emojis did not send to Twitch chat correctly. (#4840)
|
||||||
- Bugfix: Fixed capitalized channel names in log inclusion list not being logged. (#4848)
|
- Bugfix: Fixed capitalized channel names in log inclusion list not being logged. (#4848)
|
||||||
- Bugfix: Trimmed custom streamlink paths on all platforms making sure you don't accidentally add spaces at the beginning or end of its path. (#4834)
|
- Bugfix: Trimmed custom streamlink paths on all platforms making sure you don't accidentally add spaces at the beginning or end of its path. (#4834)
|
||||||
|
|
|
@ -210,7 +210,7 @@ if (CHATTERINO_PLUGINS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (BUILD_WITH_CRASHPAD)
|
if (BUILD_WITH_CRASHPAD)
|
||||||
add_subdirectory("${CMAKE_SOURCE_DIR}/lib/crashpad" EXCLUDE_FROM_ALL)
|
add_subdirectory("${CMAKE_SOURCE_DIR}/tools/crash-handler")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Used to provide a date of build in the About page (for nightly builds). Getting the actual time of
|
# Used to provide a date of build in the About page (for nightly builds). Getting the actual time of
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 89991e9910bc4c0893e45c8cfad0bdd31cc25a5c
|
|
|
@ -44,6 +44,11 @@ public:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CrashHandler *getCrashHandler() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
CommandController *getCommands() override
|
CommandController *getCommands() override
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
|
#include "singletons/CrashHandler.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Fonts.hpp"
|
#include "singletons/Fonts.hpp"
|
||||||
#include "singletons/helper/LoggingChannel.hpp"
|
#include "singletons/helper/LoggingChannel.hpp"
|
||||||
|
@ -113,6 +114,7 @@ Application::Application(Settings &_settings, Paths &_paths)
|
||||||
, toasts(&this->emplace<Toasts>())
|
, toasts(&this->emplace<Toasts>())
|
||||||
, imageUploader(&this->emplace<ImageUploader>())
|
, imageUploader(&this->emplace<ImageUploader>())
|
||||||
, seventvAPI(&this->emplace<SeventvAPI>())
|
, seventvAPI(&this->emplace<SeventvAPI>())
|
||||||
|
, crashHandler(&this->emplace<CrashHandler>())
|
||||||
|
|
||||||
, commands(&this->emplace<CommandController>())
|
, commands(&this->emplace<CommandController>())
|
||||||
, notifications(&this->emplace<NotificationController>())
|
, notifications(&this->emplace<NotificationController>())
|
||||||
|
@ -174,7 +176,9 @@ void Application::initialize(Settings &settings, Paths &paths)
|
||||||
singleton->initialize(settings, paths);
|
singleton->initialize(settings, paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add crash message
|
// Show crash message.
|
||||||
|
// On Windows, the crash message was already shown.
|
||||||
|
#ifndef Q_OS_WIN
|
||||||
if (!getArgs().isFramelessEmbed && getArgs().crashRecovery)
|
if (!getArgs().isFramelessEmbed && getArgs().crashRecovery)
|
||||||
{
|
{
|
||||||
if (auto selected =
|
if (auto selected =
|
||||||
|
@ -195,6 +199,7 @@ void Application::initialize(Settings &settings, Paths &paths)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
this->windows->updateWordTypeMask();
|
this->windows->updateWordTypeMask();
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ class FfzBadges;
|
||||||
class SeventvBadges;
|
class SeventvBadges;
|
||||||
class ImageUploader;
|
class ImageUploader;
|
||||||
class SeventvAPI;
|
class SeventvAPI;
|
||||||
|
class CrashHandler;
|
||||||
|
|
||||||
class IApplication
|
class IApplication
|
||||||
{
|
{
|
||||||
|
@ -60,6 +61,7 @@ public:
|
||||||
virtual HotkeyController *getHotkeys() = 0;
|
virtual HotkeyController *getHotkeys() = 0;
|
||||||
virtual WindowManager *getWindows() = 0;
|
virtual WindowManager *getWindows() = 0;
|
||||||
virtual Toasts *getToasts() = 0;
|
virtual Toasts *getToasts() = 0;
|
||||||
|
virtual CrashHandler *getCrashHandler() = 0;
|
||||||
virtual CommandController *getCommands() = 0;
|
virtual CommandController *getCommands() = 0;
|
||||||
virtual HighlightController *getHighlights() = 0;
|
virtual HighlightController *getHighlights() = 0;
|
||||||
virtual NotificationController *getNotifications() = 0;
|
virtual NotificationController *getNotifications() = 0;
|
||||||
|
@ -102,6 +104,7 @@ public:
|
||||||
Toasts *const toasts{};
|
Toasts *const toasts{};
|
||||||
ImageUploader *const imageUploader{};
|
ImageUploader *const imageUploader{};
|
||||||
SeventvAPI *const seventvAPI{};
|
SeventvAPI *const seventvAPI{};
|
||||||
|
CrashHandler *const crashHandler{};
|
||||||
|
|
||||||
CommandController *const commands{};
|
CommandController *const commands{};
|
||||||
NotificationController *const notifications{};
|
NotificationController *const notifications{};
|
||||||
|
@ -148,6 +151,10 @@ public:
|
||||||
{
|
{
|
||||||
return this->toasts;
|
return this->toasts;
|
||||||
}
|
}
|
||||||
|
CrashHandler *getCrashHandler() override
|
||||||
|
{
|
||||||
|
return this->crashHandler;
|
||||||
|
}
|
||||||
CommandController *getCommands() override
|
CommandController *getCommands() override
|
||||||
{
|
{
|
||||||
return this->commands;
|
return this->commands;
|
||||||
|
|
|
@ -289,8 +289,6 @@ set(SOURCE_FILES
|
||||||
messages/search/SubtierPredicate.cpp
|
messages/search/SubtierPredicate.cpp
|
||||||
messages/search/SubtierPredicate.hpp
|
messages/search/SubtierPredicate.hpp
|
||||||
|
|
||||||
providers/Crashpad.cpp
|
|
||||||
providers/Crashpad.hpp
|
|
||||||
providers/IvrApi.cpp
|
providers/IvrApi.cpp
|
||||||
providers/IvrApi.hpp
|
providers/IvrApi.hpp
|
||||||
providers/LinkResolver.cpp
|
providers/LinkResolver.cpp
|
||||||
|
@ -425,6 +423,8 @@ set(SOURCE_FILES
|
||||||
|
|
||||||
singletons/Badges.cpp
|
singletons/Badges.cpp
|
||||||
singletons/Badges.hpp
|
singletons/Badges.hpp
|
||||||
|
singletons/CrashHandler.cpp
|
||||||
|
singletons/CrashHandler.hpp
|
||||||
singletons/Emotes.cpp
|
singletons/Emotes.cpp
|
||||||
singletons/Emotes.hpp
|
singletons/Emotes.hpp
|
||||||
singletons/Fonts.cpp
|
singletons/Fonts.cpp
|
||||||
|
@ -1007,7 +1007,6 @@ endif ()
|
||||||
if (BUILD_WITH_CRASHPAD)
|
if (BUILD_WITH_CRASHPAD)
|
||||||
target_compile_definitions(${LIBRARY_PROJECT} PUBLIC CHATTERINO_WITH_CRASHPAD)
|
target_compile_definitions(${LIBRARY_PROJECT} PUBLIC CHATTERINO_WITH_CRASHPAD)
|
||||||
target_link_libraries(${LIBRARY_PROJECT} PUBLIC crashpad::client)
|
target_link_libraries(${LIBRARY_PROJECT} PUBLIC crashpad::client)
|
||||||
set_target_directory_hierarchy(crashpad_handler crashpad)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Configure compiler warnings
|
# Configure compiler warnings
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "common/Modes.hpp"
|
#include "common/Modes.hpp"
|
||||||
#include "common/NetworkManager.hpp"
|
#include "common/NetworkManager.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
|
#include "singletons/CrashHandler.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Resources.hpp"
|
#include "singletons/Resources.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
@ -99,21 +100,10 @@ namespace {
|
||||||
|
|
||||||
void showLastCrashDialog()
|
void showLastCrashDialog()
|
||||||
{
|
{
|
||||||
//#ifndef C_DISABLE_CRASH_DIALOG
|
auto *dialog = new LastRunCrashDialog;
|
||||||
// LastRunCrashDialog dialog;
|
// Use exec() over open() to block the app from being loaded
|
||||||
|
// and to be able to set the safe mode.
|
||||||
// switch (dialog.exec())
|
dialog->exec();
|
||||||
// {
|
|
||||||
// case QDialog::Accepted:
|
|
||||||
// {
|
|
||||||
// };
|
|
||||||
// break;
|
|
||||||
// default:
|
|
||||||
// {
|
|
||||||
// _exit(0);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void createRunningFile(const QString &path)
|
void createRunningFile(const QString &path)
|
||||||
|
@ -131,14 +121,13 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::steady_clock::time_point signalsInitTime;
|
std::chrono::steady_clock::time_point signalsInitTime;
|
||||||
bool restartOnSignal = false;
|
|
||||||
|
|
||||||
[[noreturn]] void handleSignal(int signum)
|
[[noreturn]] void handleSignal(int signum)
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
if (restartOnSignal &&
|
if (std::chrono::steady_clock::now() - signalsInitTime > 30s &&
|
||||||
std::chrono::steady_clock::now() - signalsInitTime > 30s)
|
getIApp()->getCrashHandler()->shouldRecover())
|
||||||
{
|
{
|
||||||
QProcess proc;
|
QProcess proc;
|
||||||
|
|
||||||
|
@ -240,9 +229,12 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
|
||||||
initResources();
|
initResources();
|
||||||
initSignalHandler();
|
initSignalHandler();
|
||||||
|
|
||||||
settings.restartOnCrash.connect([](const bool &value) {
|
#ifdef Q_OS_WIN
|
||||||
restartOnSignal = value;
|
if (getArgs().crashRecovery)
|
||||||
});
|
{
|
||||||
|
showLastCrashDialog();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
auto thread = std::thread([dir = paths.miscDirectory] {
|
auto thread = std::thread([dir = paths.miscDirectory] {
|
||||||
{
|
{
|
||||||
|
@ -279,30 +271,11 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
|
||||||
chatterino::NetworkManager::init();
|
chatterino::NetworkManager::init();
|
||||||
chatterino::Updates::instance().checkForUpdates();
|
chatterino::Updates::instance().checkForUpdates();
|
||||||
|
|
||||||
#ifdef C_USE_BREAKPAD
|
|
||||||
QBreakpadInstance.setDumpPath(getPaths()->settingsFolderPath + "/Crashes");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Running file
|
|
||||||
auto runningPath =
|
|
||||||
paths.miscDirectory + "/running_" + paths.applicationFilePathHash;
|
|
||||||
|
|
||||||
if (QFile::exists(runningPath))
|
|
||||||
{
|
|
||||||
showLastCrashDialog();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
createRunningFile(runningPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
Application app(settings, paths);
|
Application app(settings, paths);
|
||||||
app.initialize(settings, paths);
|
app.initialize(settings, paths);
|
||||||
app.run(a);
|
app.run(a);
|
||||||
app.save();
|
app.save();
|
||||||
|
|
||||||
removeRunningFile(runningPath);
|
|
||||||
|
|
||||||
if (!getArgs().dontSaveSettings)
|
if (!getArgs().dontSaveSettings)
|
||||||
{
|
{
|
||||||
pajlada::Settings::SettingManager::gSave();
|
pajlada::Settings::SettingManager::gSave();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "Args.hpp"
|
#include "Args.hpp"
|
||||||
|
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/AttachToConsole.hpp"
|
#include "util/AttachToConsole.hpp"
|
||||||
|
@ -14,6 +15,55 @@
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
QCommandLineOption hiddenOption(Args... args)
|
||||||
|
{
|
||||||
|
QCommandLineOption opt(args...);
|
||||||
|
opt.setFlags(QCommandLineOption::HiddenFromHelp);
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList extractCommandLine(
|
||||||
|
const QCommandLineParser &parser,
|
||||||
|
std::initializer_list<QCommandLineOption> options)
|
||||||
|
{
|
||||||
|
QStringList args;
|
||||||
|
for (const auto &option : options)
|
||||||
|
{
|
||||||
|
if (parser.isSet(option))
|
||||||
|
{
|
||||||
|
auto optionName = option.names().first();
|
||||||
|
if (optionName.length() == 1)
|
||||||
|
{
|
||||||
|
optionName.prepend(u'-');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
optionName.prepend("--");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto values = parser.values(option);
|
||||||
|
if (values.empty())
|
||||||
|
{
|
||||||
|
args += optionName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const auto &value : values)
|
||||||
|
{
|
||||||
|
args += optionName;
|
||||||
|
args += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
Args::Args(const QApplication &app)
|
Args::Args(const QApplication &app)
|
||||||
|
@ -23,39 +73,44 @@ Args::Args(const QApplication &app)
|
||||||
parser.addHelpOption();
|
parser.addHelpOption();
|
||||||
|
|
||||||
// Used internally by app to restart after unexpected crashes
|
// Used internally by app to restart after unexpected crashes
|
||||||
QCommandLineOption crashRecoveryOption("crash-recovery");
|
auto crashRecoveryOption = hiddenOption("crash-recovery");
|
||||||
crashRecoveryOption.setFlags(QCommandLineOption::HiddenFromHelp);
|
auto exceptionCodeOption = hiddenOption("cr-exception-code", "", "code");
|
||||||
|
auto exceptionMessageOption =
|
||||||
|
hiddenOption("cr-exception-message", "", "message");
|
||||||
|
|
||||||
// Added to ignore the parent-window option passed during native messaging
|
// Added to ignore the parent-window option passed during native messaging
|
||||||
QCommandLineOption parentWindowOption("parent-window");
|
auto parentWindowOption = hiddenOption("parent-window");
|
||||||
parentWindowOption.setFlags(QCommandLineOption::HiddenFromHelp);
|
auto parentWindowIdOption =
|
||||||
QCommandLineOption parentWindowIdOption("x-attach-split-to-window", "",
|
hiddenOption("x-attach-split-to-window", "", "window-id");
|
||||||
"window-id");
|
|
||||||
parentWindowIdOption.setFlags(QCommandLineOption::HiddenFromHelp);
|
|
||||||
|
|
||||||
// Verbose
|
// Verbose
|
||||||
QCommandLineOption verboseOption({{"v", "verbose"},
|
auto verboseOption = QCommandLineOption(
|
||||||
"Attaches to the Console on windows, "
|
QStringList{"v", "verbose"}, "Attaches to the Console on windows, "
|
||||||
"allowing you to see debug output."});
|
"allowing you to see debug output.");
|
||||||
crashRecoveryOption.setFlags(QCommandLineOption::HiddenFromHelp);
|
// Safe mode
|
||||||
QCommandLineOption safeModeOption(
|
QCommandLineOption safeModeOption(
|
||||||
"safe-mode", "Starts Chatterino without loading Plugins and always "
|
"safe-mode", "Starts Chatterino without loading Plugins and always "
|
||||||
"show the settings button.");
|
"show the settings button.");
|
||||||
|
|
||||||
parser.addOptions({
|
// Channel layout
|
||||||
{{"V", "version"}, "Displays version information."},
|
auto channelLayout = QCommandLineOption(
|
||||||
crashRecoveryOption,
|
|
||||||
parentWindowOption,
|
|
||||||
parentWindowIdOption,
|
|
||||||
verboseOption,
|
|
||||||
safeModeOption,
|
|
||||||
});
|
|
||||||
parser.addOption(QCommandLineOption(
|
|
||||||
{"c", "channels"},
|
{"c", "channels"},
|
||||||
"Joins only supplied channels on startup. Use letters with colons to "
|
"Joins only supplied channels on startup. Use letters with colons to "
|
||||||
"specify platform. Only Twitch channels are supported at the moment.\n"
|
"specify platform. Only Twitch channels are supported at the moment.\n"
|
||||||
"If platform isn't specified, default is Twitch.",
|
"If platform isn't specified, default is Twitch.",
|
||||||
"t:channel1;t:channel2;..."));
|
"t:channel1;t:channel2;...");
|
||||||
|
|
||||||
|
parser.addOptions({
|
||||||
|
{{"V", "version"}, "Displays version information."},
|
||||||
|
crashRecoveryOption,
|
||||||
|
exceptionCodeOption,
|
||||||
|
exceptionMessageOption,
|
||||||
|
parentWindowOption,
|
||||||
|
parentWindowIdOption,
|
||||||
|
verboseOption,
|
||||||
|
safeModeOption,
|
||||||
|
channelLayout,
|
||||||
|
});
|
||||||
|
|
||||||
if (!parser.parse(app.arguments()))
|
if (!parser.parse(app.arguments()))
|
||||||
{
|
{
|
||||||
|
@ -75,15 +130,25 @@ Args::Args(const QApplication &app)
|
||||||
(args.size() > 0 && (args[0].startsWith("chrome-extension://") ||
|
(args.size() > 0 && (args[0].startsWith("chrome-extension://") ||
|
||||||
args[0].endsWith(".json")));
|
args[0].endsWith(".json")));
|
||||||
|
|
||||||
if (parser.isSet("c"))
|
if (parser.isSet(channelLayout))
|
||||||
{
|
{
|
||||||
this->applyCustomChannelLayout(parser.value("c"));
|
this->applyCustomChannelLayout(parser.value(channelLayout));
|
||||||
}
|
}
|
||||||
|
|
||||||
this->verbose = parser.isSet(verboseOption);
|
this->verbose = parser.isSet(verboseOption);
|
||||||
|
|
||||||
this->printVersion = parser.isSet("V");
|
this->printVersion = parser.isSet("V");
|
||||||
this->crashRecovery = parser.isSet("crash-recovery");
|
|
||||||
|
this->crashRecovery = parser.isSet(crashRecoveryOption);
|
||||||
|
if (parser.isSet(exceptionCodeOption))
|
||||||
|
{
|
||||||
|
this->exceptionCode =
|
||||||
|
static_cast<uint32_t>(parser.value(exceptionCodeOption).toULong());
|
||||||
|
}
|
||||||
|
if (parser.isSet(exceptionMessageOption))
|
||||||
|
{
|
||||||
|
this->exceptionMessage = parser.value(exceptionMessageOption);
|
||||||
|
}
|
||||||
|
|
||||||
if (parser.isSet(parentWindowIdOption))
|
if (parser.isSet(parentWindowIdOption))
|
||||||
{
|
{
|
||||||
|
@ -97,6 +162,17 @@ Args::Args(const QApplication &app)
|
||||||
{
|
{
|
||||||
this->safeMode = true;
|
this->safeMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->currentArguments_ = extractCommandLine(parser, {
|
||||||
|
verboseOption,
|
||||||
|
safeModeOption,
|
||||||
|
channelLayout,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList Args::currentArguments() const
|
||||||
|
{
|
||||||
|
return this->currentArguments_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Args::applyCustomChannelLayout(const QString &argValue)
|
void Args::applyCustomChannelLayout(const QString &argValue)
|
||||||
|
|
|
@ -9,13 +9,37 @@
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
/// Command line arguments passed to Chatterino.
|
/// Command line arguments passed to Chatterino.
|
||||||
|
///
|
||||||
|
/// All accepted arguments:
|
||||||
|
///
|
||||||
|
/// Crash recovery:
|
||||||
|
/// --crash-recovery
|
||||||
|
/// --cr-exception-code code
|
||||||
|
/// --cr-exception-message message
|
||||||
|
///
|
||||||
|
/// Native messaging:
|
||||||
|
/// --parent-window
|
||||||
|
/// --x-attach-split-to-window=window-id
|
||||||
|
///
|
||||||
|
/// -v, --verbose
|
||||||
|
/// -V, --version
|
||||||
|
/// -c, --channels=t:channel1;t:channel2;...
|
||||||
|
/// --safe-mode
|
||||||
|
///
|
||||||
|
/// See documentation on `QGuiApplication` for documentation on Qt arguments like -platform.
|
||||||
class Args
|
class Args
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Args(const QApplication &app);
|
Args(const QApplication &app);
|
||||||
|
|
||||||
bool printVersion{};
|
bool printVersion{};
|
||||||
|
|
||||||
bool crashRecovery{};
|
bool crashRecovery{};
|
||||||
|
/// Native, platform-specific exception code from crashpad
|
||||||
|
std::optional<uint32_t> exceptionCode{};
|
||||||
|
/// Text version of the exception code. Potentially contains more context.
|
||||||
|
std::optional<QString> exceptionMessage{};
|
||||||
|
|
||||||
bool shouldRunBrowserExtensionHost{};
|
bool shouldRunBrowserExtensionHost{};
|
||||||
// Shows a single chat. Used on windows to embed in another application.
|
// Shows a single chat. Used on windows to embed in another application.
|
||||||
bool isFramelessEmbed{};
|
bool isFramelessEmbed{};
|
||||||
|
@ -28,8 +52,12 @@ public:
|
||||||
bool verbose{};
|
bool verbose{};
|
||||||
bool safeMode{};
|
bool safeMode{};
|
||||||
|
|
||||||
|
QStringList currentArguments() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void applyCustomChannelLayout(const QString &argValue);
|
void applyCustomChannelLayout(const QString &argValue);
|
||||||
|
|
||||||
|
QStringList currentArguments_;
|
||||||
};
|
};
|
||||||
|
|
||||||
void initArgs(const QApplication &app);
|
void initArgs(const QApplication &app);
|
||||||
|
|
|
@ -97,6 +97,11 @@ public:
|
||||||
return !this->hasAny(flags);
|
return !this->hasAny(flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T value() const
|
||||||
|
{
|
||||||
|
return this->value_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T value_{};
|
T value_{};
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,6 +12,8 @@ Q_LOGGING_CATEGORY(chatterinoBenchmark, "chatterino.benchmark", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoBttv, "chatterino.bttv", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoBttv, "chatterino.bttv", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoCache, "chatterino.cache", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoCache, "chatterino.cache", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoCommon, "chatterino.common", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoCommon, "chatterino.common", logThreshold);
|
||||||
|
Q_LOGGING_CATEGORY(chatterinoCrashhandler, "chatterino.crashhandler",
|
||||||
|
logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoEmoji, "chatterino.emoji", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoEmoji, "chatterino.emoji", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoEnv, "chatterino.env", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoEnv, "chatterino.env", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoFfzemotes, "chatterino.ffzemotes", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoFfzemotes, "chatterino.ffzemotes", logThreshold);
|
||||||
|
|
|
@ -8,6 +8,7 @@ Q_DECLARE_LOGGING_CATEGORY(chatterinoBenchmark);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoBttv);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoBttv);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoCache);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoCache);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoCommon);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoCommon);
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoCrashhandler);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoEmoji);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoEmoji);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoEnv);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoEnv);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoFfzemotes);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoFfzemotes);
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
#include "common/Modes.hpp"
|
#include "common/Modes.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "common/Version.hpp"
|
#include "common/Version.hpp"
|
||||||
#include "providers/Crashpad.hpp"
|
|
||||||
#include "providers/IvrApi.hpp"
|
#include "providers/IvrApi.hpp"
|
||||||
#include "providers/NetworkConfigurationProvider.hpp"
|
#include "providers/NetworkConfigurationProvider.hpp"
|
||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "RunGui.hpp"
|
#include "RunGui.hpp"
|
||||||
|
#include "singletons/CrashHandler.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "util/AttachToConsole.hpp"
|
#include "util/AttachToConsole.hpp"
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
#ifdef CHATTERINO_WITH_CRASHPAD
|
|
||||||
# include "providers/Crashpad.hpp"
|
|
||||||
|
|
||||||
# include "common/QLogging.hpp"
|
|
||||||
# include "singletons/Paths.hpp"
|
|
||||||
|
|
||||||
# include <QApplication>
|
|
||||||
# include <QDir>
|
|
||||||
# include <QString>
|
|
||||||
|
|
||||||
# include <memory>
|
|
||||||
# include <string>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
/// The name of the crashpad handler executable.
|
|
||||||
/// This varies across platforms
|
|
||||||
# if defined(Q_OS_UNIX)
|
|
||||||
const QString CRASHPAD_EXECUTABLE_NAME = QStringLiteral("crashpad_handler");
|
|
||||||
# elif defined(Q_OS_WINDOWS)
|
|
||||||
const QString CRASHPAD_EXECUTABLE_NAME = QStringLiteral("crashpad_handler.exe");
|
|
||||||
# else
|
|
||||||
# error Unsupported platform
|
|
||||||
# endif
|
|
||||||
|
|
||||||
/// Converts a QString into the platform string representation.
|
|
||||||
# if defined(Q_OS_UNIX)
|
|
||||||
std::string nativeString(const QString &s)
|
|
||||||
{
|
|
||||||
return s.toStdString();
|
|
||||||
}
|
|
||||||
# elif defined(Q_OS_WINDOWS)
|
|
||||||
std::wstring nativeString(const QString &s)
|
|
||||||
{
|
|
||||||
return s.toStdWString();
|
|
||||||
}
|
|
||||||
# else
|
|
||||||
# error Unsupported platform
|
|
||||||
# endif
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
std::unique_ptr<crashpad::CrashpadClient> installCrashHandler()
|
|
||||||
{
|
|
||||||
// Currently, the following directory layout is assumed:
|
|
||||||
// [applicationDirPath]
|
|
||||||
// │
|
|
||||||
// ├─chatterino
|
|
||||||
// │
|
|
||||||
// ╰─[crashpad]
|
|
||||||
// │
|
|
||||||
// ╰─crashpad_handler
|
|
||||||
// TODO: The location of the binary might vary across platforms
|
|
||||||
auto crashpadBinDir = QDir(QApplication::applicationDirPath());
|
|
||||||
|
|
||||||
if (!crashpadBinDir.cd("crashpad"))
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoApp) << "Cannot find crashpad directory";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (!crashpadBinDir.exists(CRASHPAD_EXECUTABLE_NAME))
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoApp) << "Cannot find crashpad handler executable";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto handlerPath = base::FilePath(nativeString(
|
|
||||||
crashpadBinDir.absoluteFilePath(CRASHPAD_EXECUTABLE_NAME)));
|
|
||||||
|
|
||||||
// Argument passed in --database
|
|
||||||
// > Crash reports are written to this database, and if uploads are enabled,
|
|
||||||
// uploaded from this database to a crash report collection server.
|
|
||||||
const auto databaseDir =
|
|
||||||
base::FilePath(nativeString(getPaths()->crashdumpDirectory));
|
|
||||||
|
|
||||||
auto client = std::make_unique<crashpad::CrashpadClient>();
|
|
||||||
|
|
||||||
// See https://chromium.googlesource.com/crashpad/crashpad/+/HEAD/handler/crashpad_handler.md
|
|
||||||
// for documentation on available options.
|
|
||||||
if (!client->StartHandler(handlerPath, databaseDir, {}, {}, {}, {}, {},
|
|
||||||
true, false))
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoApp) << "Failed to start crashpad handler";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(chatterinoApp) << "Started crashpad handler";
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,14 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#ifdef CHATTERINO_WITH_CRASHPAD
|
|
||||||
# include <client/crashpad_client.h>
|
|
||||||
|
|
||||||
# include <memory>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
std::unique_ptr<crashpad::CrashpadClient> installCrashHandler();
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
|
|
||||||
#endif
|
|
230
src/singletons/CrashHandler.cpp
Normal file
230
src/singletons/CrashHandler.cpp
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
#include "singletons/CrashHandler.hpp"
|
||||||
|
|
||||||
|
#include "common/Args.hpp"
|
||||||
|
#include "common/Literals.hpp"
|
||||||
|
#include "common/QLogging.hpp"
|
||||||
|
#include "singletons/Paths.hpp"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#ifdef CHATTERINO_WITH_CRASHPAD
|
||||||
|
# include <QApplication>
|
||||||
|
|
||||||
|
# include <memory>
|
||||||
|
# include <string>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using namespace chatterino;
|
||||||
|
using namespace literals;
|
||||||
|
|
||||||
|
/// The name of the crashpad handler executable.
|
||||||
|
/// This varies across platforms
|
||||||
|
#if defined(Q_OS_UNIX)
|
||||||
|
const QString CRASHPAD_EXECUTABLE_NAME = QStringLiteral("crashpad-handler");
|
||||||
|
#elif defined(Q_OS_WINDOWS)
|
||||||
|
const QString CRASHPAD_EXECUTABLE_NAME = QStringLiteral("crashpad-handler.exe");
|
||||||
|
#else
|
||||||
|
# error Unsupported platform
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Converts a QString into the platform string representation.
|
||||||
|
#if defined(Q_OS_UNIX)
|
||||||
|
std::string nativeString(const QString &s)
|
||||||
|
{
|
||||||
|
return s.toStdString();
|
||||||
|
}
|
||||||
|
#elif defined(Q_OS_WINDOWS)
|
||||||
|
std::wstring nativeString(const QString &s)
|
||||||
|
{
|
||||||
|
return s.toStdWString();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
# error Unsupported platform
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const QString RECOVERY_FILE = u"chatterino-recovery.json"_s;
|
||||||
|
|
||||||
|
/// The recovery options are saved outside the settings
|
||||||
|
/// to be able to read them without loading the settings.
|
||||||
|
///
|
||||||
|
/// The flags are saved in the `RECOVERY_FILE` as JSON.
|
||||||
|
std::optional<bool> readRecoverySettings(const Paths &paths)
|
||||||
|
{
|
||||||
|
QFile file(QDir(paths.crashdumpDirectory).filePath(RECOVERY_FILE));
|
||||||
|
if (!file.open(QFile::ReadOnly))
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError error{};
|
||||||
|
auto doc = QJsonDocument::fromJson(file.readAll(), &error);
|
||||||
|
if (error.error != QJsonParseError::NoError)
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoCrashhandler)
|
||||||
|
<< "Failed to parse recovery settings" << error.errorString();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto obj = doc.object();
|
||||||
|
auto shouldRecover = obj["shouldRecover"_L1];
|
||||||
|
if (!shouldRecover.isBool())
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldRecover.toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canRestart(const Paths &paths)
|
||||||
|
{
|
||||||
|
#ifdef NDEBUG
|
||||||
|
const auto &args = chatterino::getArgs();
|
||||||
|
if (args.isFramelessEmbed || args.shouldRunBrowserExtensionHost)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto settings = readRecoverySettings(paths);
|
||||||
|
if (!settings)
|
||||||
|
{
|
||||||
|
return false; // default, no settings found
|
||||||
|
}
|
||||||
|
return *settings;
|
||||||
|
#else
|
||||||
|
(void)paths;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This encodes the arguments into a single string.
|
||||||
|
///
|
||||||
|
/// The command line arguments are joined by '+'. A plus is escaped by an
|
||||||
|
/// additional plus ('++' -> '+').
|
||||||
|
///
|
||||||
|
/// The decoding happens in crash-handler/src/CommandLine.cpp
|
||||||
|
std::string encodeArguments()
|
||||||
|
{
|
||||||
|
std::string args;
|
||||||
|
for (auto arg : getArgs().currentArguments())
|
||||||
|
{
|
||||||
|
if (!args.empty())
|
||||||
|
{
|
||||||
|
args.push_back('+');
|
||||||
|
}
|
||||||
|
args += arg.replace(u'+', u"++"_s).toStdString();
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
void CrashHandler::initialize(Settings & /*settings*/, Paths &paths)
|
||||||
|
{
|
||||||
|
auto optSettings = readRecoverySettings(paths);
|
||||||
|
if (optSettings)
|
||||||
|
{
|
||||||
|
this->shouldRecover_ = *optSettings;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// By default, we don't restart after a crash.
|
||||||
|
this->saveShouldRecover(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrashHandler::saveShouldRecover(bool value)
|
||||||
|
{
|
||||||
|
this->shouldRecover_ = value;
|
||||||
|
|
||||||
|
QFile file(QDir(getPaths()->crashdumpDirectory).filePath(RECOVERY_FILE));
|
||||||
|
if (!file.open(QFile::WriteOnly | QFile::Truncate))
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoCrashhandler)
|
||||||
|
<< "Failed to open" << file.fileName();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file.write(QJsonDocument(QJsonObject{
|
||||||
|
{"shouldRecover"_L1, value},
|
||||||
|
})
|
||||||
|
.toJson(QJsonDocument::Compact));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CHATTERINO_WITH_CRASHPAD
|
||||||
|
std::unique_ptr<crashpad::CrashpadClient> installCrashHandler()
|
||||||
|
{
|
||||||
|
// Currently, the following directory layout is assumed:
|
||||||
|
// [applicationDirPath]
|
||||||
|
// ├─chatterino(.exe)
|
||||||
|
// ╰─[crashpad]
|
||||||
|
// ╰─crashpad-handler(.exe)
|
||||||
|
// TODO: The location of the binary might vary across platforms
|
||||||
|
auto crashpadBinDir = QDir(QApplication::applicationDirPath());
|
||||||
|
|
||||||
|
if (!crashpadBinDir.cd("crashpad"))
|
||||||
|
{
|
||||||
|
qCDebug(chatterinoCrashhandler) << "Cannot find crashpad directory";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!crashpadBinDir.exists(CRASHPAD_EXECUTABLE_NAME))
|
||||||
|
{
|
||||||
|
qCDebug(chatterinoCrashhandler)
|
||||||
|
<< "Cannot find crashpad handler executable";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto handlerPath = base::FilePath(nativeString(
|
||||||
|
crashpadBinDir.absoluteFilePath(CRASHPAD_EXECUTABLE_NAME)));
|
||||||
|
|
||||||
|
// Argument passed in --database
|
||||||
|
// > Crash reports are written to this database, and if uploads are enabled,
|
||||||
|
// uploaded from this database to a crash report collection server.
|
||||||
|
auto databaseDir =
|
||||||
|
base::FilePath(nativeString(getPaths()->crashdumpDirectory));
|
||||||
|
|
||||||
|
auto client = std::make_unique<crashpad::CrashpadClient>();
|
||||||
|
|
||||||
|
std::map<std::string, std::string> annotations{
|
||||||
|
{
|
||||||
|
"canRestart"s,
|
||||||
|
canRestart(*getPaths()) ? "true"s : "false"s,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exePath"s,
|
||||||
|
QApplication::applicationFilePath().toStdString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"startedAt"s,
|
||||||
|
QDateTime::currentDateTimeUtc().toString(Qt::ISODate).toStdString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exeArguments"s,
|
||||||
|
encodeArguments(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// See https://chromium.googlesource.com/crashpad/crashpad/+/HEAD/handler/crashpad_handler.md
|
||||||
|
// for documentation on available options.
|
||||||
|
if (!client->StartHandler(handlerPath, databaseDir, {}, {}, {}, annotations,
|
||||||
|
{}, true, false))
|
||||||
|
{
|
||||||
|
qCDebug(chatterinoCrashhandler) << "Failed to start crashpad handler";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(chatterinoCrashhandler) << "Started crashpad handler";
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace chatterino
|
36
src/singletons/CrashHandler.hpp
Normal file
36
src/singletons/CrashHandler.hpp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Singleton.hpp"
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#ifdef CHATTERINO_WITH_CRASHPAD
|
||||||
|
# include <client/crashpad_client.h>
|
||||||
|
|
||||||
|
# include <memory>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
class CrashHandler : public Singleton
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool shouldRecover() const
|
||||||
|
{
|
||||||
|
return this->shouldRecover_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets and saves whether Chatterino should restart on a crash
|
||||||
|
void saveShouldRecover(bool value);
|
||||||
|
|
||||||
|
void initialize(Settings &settings, Paths &paths) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool shouldRecover_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef CHATTERINO_WITH_CRASHPAD
|
||||||
|
std::unique_ptr<crashpad::CrashpadClient> installCrashHandler();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -510,7 +510,6 @@ public:
|
||||||
ThumbnailPreviewMode::AlwaysShow,
|
ThumbnailPreviewMode::AlwaysShow,
|
||||||
};
|
};
|
||||||
QStringSetting cachePath = {"/cache/path", ""};
|
QStringSetting cachePath = {"/cache/path", ""};
|
||||||
BoolSetting restartOnCrash = {"/misc/restartOnCrash", false};
|
|
||||||
BoolSetting attachExtensionToAnyProcess = {
|
BoolSetting attachExtensionToAnyProcess = {
|
||||||
"/misc/attachExtensionToAnyProcess", false};
|
"/misc/attachExtensionToAnyProcess", false};
|
||||||
BoolSetting askOnImageUpload = {"/misc/askOnImageUpload", true};
|
BoolSetting askOnImageUpload = {"/misc/askOnImageUpload", true};
|
||||||
|
|
|
@ -1,93 +1,109 @@
|
||||||
#include "LastRunCrashDialog.hpp"
|
#include "widgets/dialogs/LastRunCrashDialog.hpp"
|
||||||
|
|
||||||
#include "singletons/Updates.hpp"
|
#include "common/Args.hpp"
|
||||||
|
#include "common/Literals.hpp"
|
||||||
|
#include "common/Modes.hpp"
|
||||||
|
#include "singletons/Paths.hpp"
|
||||||
#include "util/LayoutCreator.hpp"
|
#include "util/LayoutCreator.hpp"
|
||||||
#include "util/PostToThread.hpp"
|
|
||||||
|
|
||||||
|
#include <QDesktopServices>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
|
#include <QDir>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QProcess>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
#include <QStringBuilder>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using namespace chatterino::literals;
|
||||||
|
|
||||||
|
const std::initializer_list<QString> MESSAGES = {
|
||||||
|
u"Oops..."_s, u"NotLikeThis"_s,
|
||||||
|
u"NOOOOOO"_s, u"I'm sorry"_s,
|
||||||
|
u"We're sorry"_s, u"My bad"_s,
|
||||||
|
u"FailFish"_s, u"O_o"_s,
|
||||||
|
u"Sorry :("_s, u"I blame cosmic rays"_s,
|
||||||
|
u"I blame TMI"_s, u"I blame Helix"_s,
|
||||||
|
u"Oopsie woopsie"_s,
|
||||||
|
};
|
||||||
|
|
||||||
|
QString randomMessage()
|
||||||
|
{
|
||||||
|
return *(MESSAGES.begin() +
|
||||||
|
(QRandomGenerator::global()->generate64() % MESSAGES.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
using namespace literals;
|
||||||
|
|
||||||
LastRunCrashDialog::LastRunCrashDialog()
|
LastRunCrashDialog::LastRunCrashDialog()
|
||||||
{
|
{
|
||||||
this->setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
this->setWindowFlag(Qt::WindowContextHelpButtonHint, false);
|
||||||
this->setWindowTitle("Chatterino");
|
this->setWindowTitle(u"Chatterino - " % randomMessage());
|
||||||
|
|
||||||
auto layout =
|
auto layout =
|
||||||
LayoutCreator<LastRunCrashDialog>(this).setLayoutType<QVBoxLayout>();
|
LayoutCreator<LastRunCrashDialog>(this).setLayoutType<QVBoxLayout>();
|
||||||
|
|
||||||
layout.emplace<QLabel>("The application wasn't terminated properly the "
|
QString text =
|
||||||
"last time it was executed.");
|
u"Chatterino unexpectedly crashed and restarted. "_s
|
||||||
|
"<i>You can disable automatic restarts in the settings.</i><br><br>";
|
||||||
|
|
||||||
|
#ifdef CHATTERINO_WITH_CRASHPAD
|
||||||
|
auto reportsDir =
|
||||||
|
QDir(getPaths()->crashdumpDirectory).filePath(u"reports"_s);
|
||||||
|
text += u"A <b>crash report</b> has been saved to "
|
||||||
|
"<a href=\"file:///" %
|
||||||
|
reportsDir % u"\">" % reportsDir % u"</a>.<br>";
|
||||||
|
|
||||||
|
if (getArgs().exceptionCode)
|
||||||
|
{
|
||||||
|
text += u"The last run crashed with code <code>0x" %
|
||||||
|
QString::number(*getArgs().exceptionCode, 16) % u"</code>";
|
||||||
|
|
||||||
|
if (getArgs().exceptionMessage)
|
||||||
|
{
|
||||||
|
text += u" (" % *getArgs().exceptionMessage % u")";
|
||||||
|
}
|
||||||
|
|
||||||
|
text += u".<br>"_s;
|
||||||
|
}
|
||||||
|
|
||||||
|
text +=
|
||||||
|
"Crash reports are <b>only stored locally</b> and never uploaded.<br>"
|
||||||
|
"<br>Please <a "
|
||||||
|
"href=\"https://github.com/Chatterino/chatterino2/issues/new\">report "
|
||||||
|
"the crash</a> "
|
||||||
|
u"so it can be prevented in the future."_s;
|
||||||
|
|
||||||
|
if (Modes::instance().isNightly)
|
||||||
|
{
|
||||||
|
text += u" Make sure you're using the latest nightly version!"_s;
|
||||||
|
}
|
||||||
|
|
||||||
|
text +=
|
||||||
|
u"<br>For more information, <a href=\"https://wiki.chatterino.com/Crash%20Analysis/\">consult the wiki</a>."_s;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto label = layout.emplace<QLabel>(text);
|
||||||
|
label->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||||
|
label->setOpenExternalLinks(true);
|
||||||
|
label->setWordWrap(true);
|
||||||
|
|
||||||
layout->addSpacing(16);
|
layout->addSpacing(16);
|
||||||
|
|
||||||
// auto update = layout.emplace<QLabel>();
|
|
||||||
auto buttons = layout.emplace<QDialogButtonBox>();
|
auto buttons = layout.emplace<QDialogButtonBox>();
|
||||||
|
|
||||||
// auto *installUpdateButton = buttons->addButton("Install Update",
|
auto *okButton = buttons->addButton(u"Ok"_s, QDialogButtonBox::AcceptRole);
|
||||||
// QDialogButtonBox::NoRole); installUpdateButton->setEnabled(false);
|
|
||||||
// QObject::connect(installUpdateButton, &QPushButton::clicked, [this,
|
|
||||||
// update]() mutable {
|
|
||||||
// auto &updateManager = UpdateManager::instance();
|
|
||||||
|
|
||||||
// updateManager.installUpdates();
|
|
||||||
// this->setEnabled(false);
|
|
||||||
// update->setText("Downloading updates...");
|
|
||||||
// });
|
|
||||||
|
|
||||||
auto *okButton =
|
|
||||||
buttons->addButton("Ignore", QDialogButtonBox::ButtonRole::NoRole);
|
|
||||||
QObject::connect(okButton, &QPushButton::clicked, [this] {
|
QObject::connect(okButton, &QPushButton::clicked, [this] {
|
||||||
this->accept();
|
this->accept();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Updates
|
|
||||||
// auto updateUpdateLabel = [update]() mutable {
|
|
||||||
// auto &updateManager = UpdateManager::instance();
|
|
||||||
|
|
||||||
// switch (updateManager.getStatus()) {
|
|
||||||
// case UpdateManager::None: {
|
|
||||||
// update->setText("Not checking for updates.");
|
|
||||||
// } break;
|
|
||||||
// case UpdateManager::Searching: {
|
|
||||||
// update->setText("Checking for updates...");
|
|
||||||
// } break;
|
|
||||||
// case UpdateManager::UpdateAvailable: {
|
|
||||||
// update->setText("Update available.");
|
|
||||||
// } break;
|
|
||||||
// case UpdateManager::NoUpdateAvailable: {
|
|
||||||
// update->setText("No update abailable.");
|
|
||||||
// } break;
|
|
||||||
// case UpdateManager::SearchFailed: {
|
|
||||||
// update->setText("Error while searching for update.\nEither
|
|
||||||
// the update service is "
|
|
||||||
// "temporarily down or there is an issue
|
|
||||||
// with your installation.");
|
|
||||||
// } break;
|
|
||||||
// case UpdateManager::Downloading: {
|
|
||||||
// update->setText(
|
|
||||||
// "Downloading the update. Chatterino will close once
|
|
||||||
// the download is done.");
|
|
||||||
// } break;
|
|
||||||
// case UpdateManager::DownloadFailed: {
|
|
||||||
// update->setText("Download failed.");
|
|
||||||
// } break;
|
|
||||||
// case UpdateManager::WriteFileFailed: {
|
|
||||||
// update->setText("Writing the update file to the hard drive
|
|
||||||
// failed.");
|
|
||||||
// } break;
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// updateUpdateLabel();
|
|
||||||
// this->signalHolder_.managedConnect(updateManager.statusUpdated,
|
|
||||||
// [updateUpdateLabel](auto) mutable {
|
|
||||||
// postToThread([updateUpdateLabel]() mutable { updateUpdateLabel();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <pajlada/signals/signalholder.hpp>
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -9,9 +8,6 @@ class LastRunCrashDialog : public QDialog
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LastRunCrashDialog();
|
LastRunCrashDialog();
|
||||||
|
|
||||||
private:
|
|
||||||
pajlada::Signals::SignalHolder signalHolder_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "controllers/sound/ISoundController.hpp"
|
#include "controllers/sound/ISoundController.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
|
#include "singletons/CrashHandler.hpp"
|
||||||
#include "singletons/Fonts.hpp"
|
#include "singletons/Fonts.hpp"
|
||||||
#include "singletons/NativeMessaging.hpp"
|
#include "singletons/NativeMessaging.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
|
@ -875,8 +876,14 @@ void GeneralPage::initLayout(GeneralPageView &layout)
|
||||||
s.openLinksIncognito);
|
s.openLinksIncognito);
|
||||||
}
|
}
|
||||||
|
|
||||||
layout.addCheckbox(
|
layout.addCustomCheckbox(
|
||||||
"Restart on crash", s.restartOnCrash, false,
|
"Restart on crash (requires restart)",
|
||||||
|
[] {
|
||||||
|
return getApp()->crashHandler->shouldRecover();
|
||||||
|
},
|
||||||
|
[](bool on) {
|
||||||
|
return getApp()->crashHandler->saveShouldRecover(on);
|
||||||
|
},
|
||||||
"When possible, restart Chatterino if the program crashes");
|
"When possible, restart Chatterino if the program crashes");
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX) && !defined(NO_QTKEYCHAIN)
|
#if defined(Q_OS_LINUX) && !defined(NO_QTKEYCHAIN)
|
||||||
|
|
|
@ -125,6 +125,28 @@ QCheckBox *GeneralPageView::addCheckbox(const QString &text,
|
||||||
return check;
|
return check;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QCheckBox *GeneralPageView::addCustomCheckbox(const QString &text,
|
||||||
|
const std::function<bool()> &load,
|
||||||
|
std::function<void(bool)> save,
|
||||||
|
const QString &toolTipText)
|
||||||
|
{
|
||||||
|
auto *check = new QCheckBox(text);
|
||||||
|
this->addToolTip(*check, toolTipText);
|
||||||
|
|
||||||
|
check->setChecked(load());
|
||||||
|
|
||||||
|
QObject::connect(check, &QCheckBox::toggled, this,
|
||||||
|
[save = std::move(save)](bool state) {
|
||||||
|
save(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
this->addWidget(check);
|
||||||
|
|
||||||
|
this->groups_.back().widgets.push_back({check, {text}});
|
||||||
|
|
||||||
|
return check;
|
||||||
|
}
|
||||||
|
|
||||||
ComboBox *GeneralPageView::addDropdown(const QString &text,
|
ComboBox *GeneralPageView::addDropdown(const QString &text,
|
||||||
const QStringList &list,
|
const QStringList &list,
|
||||||
QString toolTipText)
|
QString toolTipText)
|
||||||
|
|
|
@ -103,6 +103,11 @@ public:
|
||||||
/// @param inverse Inverses true to false and vice versa
|
/// @param inverse Inverses true to false and vice versa
|
||||||
QCheckBox *addCheckbox(const QString &text, BoolSetting &setting,
|
QCheckBox *addCheckbox(const QString &text, BoolSetting &setting,
|
||||||
bool inverse = false, QString toolTipText = {});
|
bool inverse = false, QString toolTipText = {});
|
||||||
|
QCheckBox *addCustomCheckbox(const QString &text,
|
||||||
|
const std::function<bool()> &load,
|
||||||
|
std::function<void(bool)> save,
|
||||||
|
const QString &toolTipText = {});
|
||||||
|
|
||||||
ComboBox *addDropdown(const QString &text, const QStringList &items,
|
ComboBox *addDropdown(const QString &text, const QStringList &items,
|
||||||
QString toolTipText = {});
|
QString toolTipText = {});
|
||||||
ComboBox *addDropdown(const QString &text, const QStringList &items,
|
ComboBox *addDropdown(const QString &text, const QStringList &items,
|
||||||
|
|
1
tools/crash-handler
Submodule
1
tools/crash-handler
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 9753fe802710b2df00f2287ec2e1ca78c251d085
|
Loading…
Reference in a new issue