From e7ab978a30ac82afeb0759cbdbf9dcfe0e4973b6 Mon Sep 17 00:00:00 2001 From: SuperDude88 <82904174+SuperDude88@users.noreply.github.com> Date: Sun, 10 May 2026 20:37:22 -0400 Subject: [PATCH] Crash Reporting Popup (#879) * Initial Draft - Add draft crash report window on startup If you want to disable them before/during startup, there is a command line option to force it * Fixes - Update language to be more precise, consistent with settings menu - Actually shut down reporting properly if you disable it - Fix my silly syntax errors * Update text & use Sentry consent --------- Co-authored-by: Luke Street --- CMakeLists.txt | 6 ++ files.cmake | 2 + include/dusk/crash_reporting.h | 17 ++++- include/dusk/settings.h | 1 - src/dusk/crash_reporting.cpp | 131 +++++++++++++++++---------------- src/dusk/settings.cpp | 2 - src/dusk/ui/reporting.cpp | 113 ++++++++++++++++++++++++++++ src/dusk/ui/reporting.hpp | 28 +++++++ src/dusk/ui/settings.cpp | 32 +++++--- src/m_Do/m_Do_main.cpp | 18 ++++- 10 files changed, 264 insertions(+), 86 deletions(-) create mode 100644 src/dusk/ui/reporting.cpp create mode 100644 src/dusk/ui/reporting.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c4f3180f1..df0afc5b86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -444,6 +444,12 @@ target_link_libraries(dusk PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBR target_precompile_headers(dusk PRIVATE "$<$:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>") if (TARGET crashpad_handler) add_dependencies(dusk crashpad_handler) + add_custom_command(TARGET dusk POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$" + COMMENT "Copying crashpad handler" + ) endif () if (ANDROID) diff --git a/files.cmake b/files.cmake index d0bcad2d30..d35d95b8a2 100644 --- a/files.cmake +++ b/files.cmake @@ -1495,6 +1495,8 @@ set(DUSK_FILES src/dusk/ui/prelaunch.hpp src/dusk/ui/preset.cpp src/dusk/ui/preset.hpp + src/dusk/ui/reporting.cpp + src/dusk/ui/reporting.hpp src/dusk/ui/select_button.cpp src/dusk/ui/select_button.hpp src/dusk/ui/settings.cpp diff --git a/include/dusk/crash_reporting.h b/include/dusk/crash_reporting.h index dfc5bde817..c42079a6f6 100644 --- a/include/dusk/crash_reporting.h +++ b/include/dusk/crash_reporting.h @@ -1,8 +1,17 @@ #pragma once -namespace dusk { +namespace dusk::crash_reporting { -void InitializeCrashReporting(); -void ShutdownCrashReporting(); +enum class Consent { + Unavailable, + Unknown, + Given, + Revoked, +}; -} // namespace dusk +void initialize(); +void shutdown(); +Consent get_consent(); +void set_consent(bool enabled); + +} // namespace dusk::crash_reporting diff --git a/include/dusk/settings.h b/include/dusk/settings.h index f74a4bf61f..709c3eb329 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -184,7 +184,6 @@ struct UserSettings { ConfigVar skipPreLaunchUI; ConfigVar showPipelineCompilation; ConfigVar wasPresetChosen; - ConfigVar enableCrashReporting; ConfigVar checkForUpdates; ConfigVar cardFileType; ConfigVar enableAdvancedSettings; diff --git a/src/dusk/crash_reporting.cpp b/src/dusk/crash_reporting.cpp index 657e5ef5d2..5c75388361 100644 --- a/src/dusk/crash_reporting.cpp +++ b/src/dusk/crash_reporting.cpp @@ -4,7 +4,6 @@ #include "dusk/dusk.h" #include "dusk/logging.h" #include "dusk/main.h" -#include "dusk/settings.h" #include "version.h" #include @@ -13,114 +12,83 @@ #include #include -#include "SDL3/SDL_filesystem.h" - #if DUSK_ENABLE_SENTRY_NATIVE #include #endif -namespace dusk { +namespace dusk::crash_reporting { namespace { #if DUSK_ENABLE_SENTRY_NATIVE bool g_sentryInitialized = false; -bool IsTruthy(std::string_view value) { - return value == "1" || value == "true" || value == "TRUE" || value == "yes" - || value == "YES" || value == "on" || value == "ON"; +bool truthy(std::string_view value) { + return value == "1" || value == "true" || value == "TRUE" || value == "yes" || value == "YES" || + value == "on" || value == "ON"; } -std::string GetEnvOrEmpty(const char* name) { +std::string env_or_empty(const char* name) { if (const char* value = std::getenv(name)) { return value; } return {}; } -bool GetEffectiveEnabled() { - const std::string env = GetEnvOrEmpty("DUSK_SENTRY_ENABLED"); - if (!env.empty()) { - return IsTruthy(env); - } - return getSettings().backend.enableCrashReporting; +bool disabled_by_env() { + const std::string env = env_or_empty("DUSK_SENTRY_ENABLED"); + return !env.empty() && !truthy(env); } -std::string GetEffectiveDsn() { - const std::string env = GetEnvOrEmpty("DUSK_SENTRY_DSN"); +std::string effective_dsn() { + const std::string env = env_or_empty("DUSK_SENTRY_DSN"); if (!env.empty()) { return env; } return DUSK_SENTRY_DSN; } -bool GetEffectiveDebug() { - const std::string env = GetEnvOrEmpty("DUSK_SENTRY_DEBUG"); +bool effective_debug() { + const std::string env = env_or_empty("DUSK_SENTRY_DEBUG"); if (!env.empty()) { - return IsTruthy(env); + return truthy(env); } return false; } -std::string GetReleaseName() { +std::string release_name() { return std::string(AppName) + "@" DUSK_WC_DESCRIBE; } -std::filesystem::path GetSentryDatabasePath() { +std::filesystem::path sentry_database_path() { return dusk::ConfigPath / "sentry"; } -std::filesystem::path GetLogAttachmentPath() { +std::filesystem::path log_attachment_path() { if (const char* logPath = GetLogFilePath()) { return logPath; } return {}; } -std::filesystem::path GetCrashpadHandlerPath() { - const char* basePath = SDL_GetBasePath(); - if (!basePath) { - return {}; - } - - const std::filesystem::path handlerDir(basePath); -#if _WIN32 - return handlerDir / "crashpad_handler.exe"; -#else - return handlerDir / "crashpad_handler"; -#endif -} - -void ConfigurePathOptions(sentry_options_t* options) { - const auto databasePath = GetSentryDatabasePath(); +void configure_path_options(sentry_options_t* options) { + const auto databasePath = sentry_database_path(); std::error_code ec; std::filesystem::create_directories(databasePath, ec); if (ec) { - DuskLog.warn("Unable to create Sentry database path '{}': {}", - databasePath.string(), ec.message()); + DuskLog.warn( + "Unable to create Sentry database path '{}': {}", databasePath.string(), ec.message()); } #if _WIN32 const std::wstring databasePathWide = databasePath.wstring(); sentry_options_set_database_pathw(options, databasePathWide.c_str()); - - const auto handlerPath = GetCrashpadHandlerPath(); - if (!handlerPath.empty()) { - const std::wstring handlerPathWide = handlerPath.wstring(); - sentry_options_set_handler_pathw(options, handlerPathWide.c_str()); - } #else const std::string databasePathUtf8 = databasePath.string(); sentry_options_set_database_path(options, databasePathUtf8.c_str()); - - const auto handlerPath = GetCrashpadHandlerPath(); - if (!handlerPath.empty()) { - const std::string handlerPathUtf8 = handlerPath.string(); - sentry_options_set_handler_path(options, handlerPathUtf8.c_str()); - } #endif - const auto logPath = GetLogAttachmentPath(); + const auto logPath = log_attachment_path(); if (!logPath.empty()) { #if _WIN32 sentry_options_add_attachmentw(options, logPath.wstring().c_str()); @@ -133,32 +101,29 @@ void ConfigurePathOptions(sentry_options_t* options) { } // namespace -void InitializeCrashReporting() { +void initialize() { #if DUSK_ENABLE_SENTRY_NATIVE - if (g_sentryInitialized) { + if (g_sentryInitialized || disabled_by_env()) { return; } - if (!GetEffectiveEnabled()) { - return; - } - - const std::string dsn = GetEffectiveDsn(); + const std::string dsn = effective_dsn(); if (dsn.empty()) { DuskLog.warn("Crash reporting is enabled but no Sentry DSN is configured"); return; } - const std::string release = GetReleaseName(); + const std::string release = release_name(); sentry_options_t* options = sentry_options_new(); sentry_options_set_dsn(options, dsn.c_str()); sentry_options_set_release(options, release.c_str()); sentry_options_set_environment(options, DUSK_SENTRY_ENVIRONMENT); - sentry_options_set_debug(options, GetEffectiveDebug() ? 1 : 0); + sentry_options_set_debug(options, effective_debug() ? 1 : 0); + sentry_options_set_require_user_consent(options, 1); sentry_options_set_cache_keep(options, 1); sentry_options_set_max_breadcrumbs(options, 100); - ConfigurePathOptions(options); + configure_path_options(options); if (sentry_init(options) != 0) { DuskLog.warn("Failed to initialize Sentry crash reporting"); @@ -173,7 +138,7 @@ void InitializeCrashReporting() { #endif } -void ShutdownCrashReporting() { +void shutdown() { #if DUSK_ENABLE_SENTRY_NATIVE if (!g_sentryInitialized) { return; @@ -184,4 +149,40 @@ void ShutdownCrashReporting() { #endif } -} // namespace dusk +Consent get_consent() { +#if DUSK_ENABLE_SENTRY_NATIVE + if (!g_sentryInitialized) { + return Consent::Unavailable; + } + + switch (sentry_user_consent_get()) { + case SENTRY_USER_CONSENT_GIVEN: + return Consent::Given; + case SENTRY_USER_CONSENT_REVOKED: + return Consent::Revoked; + case SENTRY_USER_CONSENT_UNKNOWN: + default: + return Consent::Unknown; + } +#else + return Consent::Unavailable; +#endif +} + +void set_consent(bool enabled) { +#if DUSK_ENABLE_SENTRY_NATIVE + if (!g_sentryInitialized) { + return; + } + + if (enabled) { + sentry_user_consent_give(); + } else { + sentry_user_consent_revoke(); + } +#else + (void)enabled; +#endif +} + +} // namespace dusk::crash_reporting diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index f0d10d4226..6880b407f7 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -121,7 +121,6 @@ UserSettings g_userSettings = { .skipPreLaunchUI {"backend.skipPreLaunchUI", false}, .showPipelineCompilation {"backend.showPipelineCompilation", false}, .wasPresetChosen {"backend.wasPresetChosen", false}, - .enableCrashReporting {"backend.enableCrashReporting", true}, .checkForUpdates {"backend.checkForUpdates", true}, .cardFileType {"backend.cardFileType", static_cast(CARD_GCIFOLDER)}, .enableAdvancedSettings {"backend.enableAdvancedSettings", false}, @@ -228,7 +227,6 @@ void registerSettings() { Register(g_userSettings.backend.skipPreLaunchUI); Register(g_userSettings.backend.showPipelineCompilation); Register(g_userSettings.backend.wasPresetChosen); - Register(g_userSettings.backend.enableCrashReporting); Register(g_userSettings.backend.checkForUpdates); Register(g_userSettings.backend.cardFileType); Register(g_userSettings.backend.enableAdvancedSettings); diff --git a/src/dusk/ui/reporting.cpp b/src/dusk/ui/reporting.cpp new file mode 100644 index 0000000000..e32a9cbc20 --- /dev/null +++ b/src/dusk/ui/reporting.cpp @@ -0,0 +1,113 @@ +#if DUSK_ENABLE_SENTRY_NATIVE + +#include "reporting.hpp" + +#include "button.hpp" +#include "dusk/crash_reporting.h" +#include "ui.hpp" + +#include + +namespace dusk::ui { + +CrashReportWindow::CrashReportWindow() : WindowSmall("modal", "modal-dialog") { + mDialog->SetClass("modal-dialog", true); + + auto* header = append(mDialog, "div"); + header->SetClass("modal-header", true); + + auto* title = append(header, "div"); + title->SetClass("modal-title", true); + title->SetInnerRML("Send Crash Reports"); + + auto* headIcon = append(header, "icon"); + headIcon->SetClass("question-mark", true); + + auto* intro = append(mDialog, "div"); + intro->SetClass("modal-body", true); + intro->SetInnerRML( + "Dusk can automatically send crash reports to the developers. Crash reports contain the " + "following:" + "
• Operating system version
• CPU architecture
• GPU model & driver version" + "
• File paths (may include account username)
• Stack trace

" + "This can be changed in the Settings menu at any time."); + + auto* grid = append(mDialog, "div"); + grid->SetClass("preset-grid", true); + + struct OptionInfo { + const char* name; + const char* desc; + void (*apply)(); + }; + + static constexpr OptionInfo kOptions[] = { + {"Enable", + "Send crash reports to Dusk developers. Reports will include the information described " + "above.", + [] { crash_reporting::set_consent(true); }}, + {"Disable", + "Do not send crash reports. This may make it more difficult to resolve issues you " + "encounter.", + [] { crash_reporting::set_consent(false); }}, + }; + + for (const auto& option : kOptions) { + auto* col = append(grid, "div"); + col->SetClass("preset-col", true); + + auto btn = std::make_unique