mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-06 01:53:25 -08:00
* [CrashReporting] Mock dialog, sentry skeleton and CI prerequisites Mock: DlgCrashReport preview dialog (no account needed, redacted log preview), CrashReportingMode Ask/Always/Never setting in DebugSettings, --simulate-crash flag and Help-menu preview entry. Send writes a local mock JSON only. Vendor sentry-native 0.17.3 via FetchContent behind WITH_CRASH_REPORTING (breakpad everywhere, backend none under sanitizers) plus a CrashReporter facade with early init and post-loop shutdown. DSN is a placeholder: empty CMake default with COCKATRICE_SENTRY_DSN env override, so SDK init is skipped and mock behavior is unchanged until a real DSN is configured. CI prerequisites riding here so every layer below builds green: libcurl dev plus CA bundles in the Linux images and CodeQL env, sentry Qt integration off (its own find_package cannot see the macOS thin Qt layout), and the StrFunc Using construction makensis requires. Tests: ctest green, format clean, code plus UI gates PASS. * [CrashReporting] Forward mode changes to SDK and fix Linux CI opt-in Connects PersonalSettings::crashReportingModeChanged to giveConsent()/revokeConsent() so switching from AlwaysSend to NeverSend (or Ask) mid-session reaches the SDK instead of leaving consent given until restart. Forwards WITH_CRASH_REPORTING into the docker container the way CCACHE_DIR is, so the Linux package leg actually builds with crash reporting ON instead of silently building OFF. * [CrashReporting] Give crash-report logging its own category per review * [CrashReporting] Drop unused DebugSettings constructor visibility per review * [CrashReporting] Document reporting modes on the enum per review * [CrashReporting] Match the dump scan to the writer's temp fallback Review nit: when the handler's SHGetKnownFolderPath lookup fails it writes to <temp>/../.., but the startup scan looked in QDir::tempPath(), so a dump produced through the fallback would never be found and its crash would never offer to be reported. Walk up the same two levels in the scan. The path is Windows-only (#ifdef Q_OS_WIN); the statements were compiled and run standalone against Qt 6 to confirm the API use. * [CrashReporting] Complete the dump-scan fallback path The previous commit walked the scan's fallback up two levels but dropped the CrashDumps/Cockatrice suffix that the writer appends unconditionally, so a dump written through the fallback was still invisible to the scan. Evaluate the writer's expression verbatim rather than mirroring it with QDir: cdUp() is existence-checked and silently leaves the path alone when the directory is missing, parent_path() is not, and QDir::absoluteFilePath() joins the shared suffix without risking a doubled separator when the walk lands on a root. Verified by compiling the block verbatim against Qt 6 with -Wextra -Werror and comparing it with the writer's join for both the normal and the fallback case. Also record that %LOCALAPPDATA% is the scan's stand-in for FOLDERID_LocalAppData and why the two normally agree. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
542 lines
23 KiB
C++
542 lines
23 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2008 by Max-Wilhelm Bruker *
|
|
* brukie@gmx.net *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
***************************************************************************/
|
|
|
|
#include "main.h"
|
|
|
|
#include "client/crash/crash_reporter.h"
|
|
#include "client/network/update/card_spoiler/spoiler_background_updater.h"
|
|
#include "client/settings/cache_settings.h"
|
|
#include "client/sound_engine.h"
|
|
#include "client/url_scheme_event_filter.h"
|
|
#include "database/interface/settings_card_preference_provider.h"
|
|
#include "interface/intents/intent.h"
|
|
#include "interface/intents/intent_open_local_deck.h"
|
|
#include "interface/logger.h"
|
|
#include "interface/pixel_map_generator.h"
|
|
#include "interface/theme_manager.h"
|
|
#include "interface/widgets/dialogs/dlg_crash_report.h"
|
|
#include "interface/window_main.h"
|
|
#include "libcockatrice/card/database/card_database.h"
|
|
#include "libcockatrice/rng/rng_abstract.h"
|
|
#include "single_instance_manager.h"
|
|
#include "version_string.h"
|
|
|
|
#include <QApplication>
|
|
#include <QByteArray>
|
|
#include <QByteArrayView>
|
|
#include <QCommandLineOption>
|
|
#include <QCommandLineParser>
|
|
#include <QCoreApplication>
|
|
#include <QCryptographicHash>
|
|
#include <QDebug>
|
|
#include <QDialog>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QGuiApplication>
|
|
#include <QIcon>
|
|
#include <QList>
|
|
#include <QLocale>
|
|
#include <QMessageBox>
|
|
#include <QMessageLogger>
|
|
#include <QNetworkInterface>
|
|
#include <QObject>
|
|
#include <QRegularExpression>
|
|
#include <QRegularExpressionMatch>
|
|
#include <QSharedPointer>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
#include <QStringLiteral>
|
|
#include <QTranslator>
|
|
#include <QUrl>
|
|
#include <QtGlobal>
|
|
#include <algorithm>
|
|
#include <libcockatrice/card/database/card_database_manager.h>
|
|
#include <libcockatrice/rng/rng_sfmt.h>
|
|
#include <libcockatrice/settings/card_database_settings.h>
|
|
#include <libcockatrice/settings/interface_settings.h>
|
|
#include <libcockatrice/settings/network_settings.h>
|
|
#include <libcockatrice/settings/personal_settings.h>
|
|
#include <libcockatrice/utility/cryptoutil.h>
|
|
#include <libcockatrice/utility/translation_loader.h>
|
|
#include <qlogging.h>
|
|
#include <qnamespace.h>
|
|
#include <qsystemdetection.h>
|
|
|
|
QTranslator *translator, *qtTranslator;
|
|
RNG_Abstract *rng;
|
|
SoundEngine *soundEngine;
|
|
QSystemTrayIcon *trayIcon;
|
|
ThemeManager *themeManager;
|
|
|
|
const QString translationPrefix = "cockatrice";
|
|
QString translationPath;
|
|
|
|
static void CockatriceLogger(QtMsgType type, const QMessageLogContext &ctx, const QString &message)
|
|
{
|
|
QString logMessage = qFormatLogMessage(type, ctx, message);
|
|
|
|
// Regular expression to match the full path in the square brackets and extract only the filename and line number
|
|
QRegularExpression regex(R"(\[(?:.:)?[\/\\].*[\/\\]([^\/\\]+\:\d+)\])");
|
|
QRegularExpressionMatch match = regex.match(logMessage);
|
|
|
|
if (match.hasMatch()) {
|
|
// Extract the filename and line number (e.g., "main.cpp:211")
|
|
QString filenameLine = match.captured(1);
|
|
|
|
// Replace the full path in square brackets with just the filename and line number
|
|
logMessage.replace(match.captured(0), QString("[%1]").arg(filenameLine));
|
|
}
|
|
|
|
Logger::getInstance().log(type, ctx, logMessage);
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
// clang-format off
|
|
#include <Windows.h>
|
|
#include <DbgHelp.h>
|
|
#include <ShlObj.h>
|
|
#include <ctime>
|
|
#include <filesystem>
|
|
|
|
#pragma comment(lib, "DbgHelp.lib") // Link the DbgHelp library
|
|
// clang-format on
|
|
|
|
LONG WINAPI CockatriceUnhandledExceptionFilter(EXCEPTION_POINTERS *exceptionPointers)
|
|
{
|
|
std::filesystem::path path;
|
|
|
|
// Find %LOCALAPPDATA% (or cheat at finding it)
|
|
wchar_t *localAppDataFolder;
|
|
if (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &localAppDataFolder) != S_OK) {
|
|
path = std::filesystem::temp_directory_path().parent_path().parent_path();
|
|
} else {
|
|
path = std::filesystem::path(localAppDataFolder);
|
|
}
|
|
|
|
// Plan on writing crash files into %LOCALAPPDATA%/CrashDumps/Cockatrice/cockatrice.crash.*.dmp
|
|
path /= "CrashDumps";
|
|
path /= "Cockatrice";
|
|
if (!std::filesystem::exists(path)) {
|
|
std::filesystem::create_directories(path);
|
|
}
|
|
path /= "cockatrice.crash." + std::to_string(std::time(0)) + ".dmp";
|
|
|
|
// Create and write crash files
|
|
#ifdef UNICODE
|
|
HANDLE hDumpFile =
|
|
CreateFile(path.wstring().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
#else
|
|
HANDLE hDumpFile =
|
|
CreateFile(path.string().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
#endif
|
|
|
|
MINIDUMP_EXCEPTION_INFORMATION mei;
|
|
mei.ExceptionPointers = exceptionPointers;
|
|
mei.ThreadId = GetCurrentThreadId();
|
|
mei.ClientPointers = 1;
|
|
|
|
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpWithFullMemory, &mei, nullptr,
|
|
nullptr);
|
|
|
|
CloseHandle(hDumpFile);
|
|
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
#endif
|
|
|
|
void installNewTranslator()
|
|
{
|
|
QString lang = SettingsCache::instance().personal().getLang();
|
|
|
|
// Qt's own strings and ours need a translator each: QTranslator discards its previous contents
|
|
// on every load, so loading both files into one translator silently drops the first of them.
|
|
TranslationLoader::loadQt(*qtTranslator, lang);
|
|
qApp->installTranslator(qtTranslator);
|
|
|
|
TranslationLoader::loadApplication(*translator, translationPrefix, lang,
|
|
TranslationLoader::applicationTranslationPaths(translationPath));
|
|
qApp->installTranslator(translator);
|
|
}
|
|
|
|
QString const generateClientID()
|
|
{
|
|
QString macList;
|
|
for (const QNetworkInterface &networkInterface : QNetworkInterface::allInterfaces()) {
|
|
if (networkInterface.hardwareAddress() != "") {
|
|
if (networkInterface.hardwareAddress() != "00:00:00:00:00:00:00:E0") {
|
|
macList += networkInterface.hardwareAddress() + ".";
|
|
}
|
|
}
|
|
}
|
|
QString strClientID = QCryptographicHash::hash(macList.toUtf8(), QCryptographicHash::Sha1).toHex().right(15);
|
|
return strClientID;
|
|
}
|
|
|
|
static QString redactActivationUrl(const QString &url)
|
|
{
|
|
// Activation URLs carry secrets in their query string (e.g. the deck share
|
|
// token); log only the scheme and the action (cockatrice://opendeck), never
|
|
// the parameters.
|
|
if (!url.startsWith(QStringLiteral("cockatrice://"))) {
|
|
return url;
|
|
}
|
|
const QUrl parsed(url);
|
|
return parsed.scheme() + "://" + parsed.host();
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
SetUnhandledExceptionFilter(CockatriceUnhandledExceptionFilter);
|
|
#endif
|
|
|
|
// Logging setup
|
|
#ifdef Q_OS_APPLE
|
|
// <build>/cockatrice/cockatrice.app/Contents/MacOS/cockatrice
|
|
const QByteArray configPath = "../../../qtlogging.ini";
|
|
#elif defined(Q_OS_UNIX)
|
|
// <build>/cockatrice/cockatrice
|
|
const QByteArray configPath = "./qtlogging.ini";
|
|
#elif defined(Q_OS_WIN)
|
|
// <build>/cockatrice/Debug/cockatrice.exe
|
|
const QByteArray configPath = "../qtlogging.ini";
|
|
#else
|
|
const QByteArray configPath = "";
|
|
#endif
|
|
|
|
if (!qEnvironmentVariableIsSet(("QT_LOGGING_CONF"))) {
|
|
// Set the QT_LOGGING_CONF environment variable
|
|
qputenv("QT_LOGGING_CONF", configPath);
|
|
}
|
|
|
|
qSetMessagePattern(
|
|
"\033[0m[%{time yyyy-MM-dd h:mm:ss.zzz} "
|
|
"%{if-debug}\033[36mD%{endif}%{if-info}\033[32mI%{endif}%{if-warning}\033[33mW%{endif}%{if-critical}\033[31mC%{"
|
|
"endif}%{if-fatal}\033[1;31mF%{endif}\033[0m] [%{function}] - %{message} [%{file}:%{line}]");
|
|
QApplication app(argc, argv);
|
|
|
|
#ifdef Q_OS_MAC
|
|
UrlSchemeEventFilter cockatriceFilter(QStringList{QStringLiteral("cockatrice")});
|
|
|
|
QStringList pendingMacUrls;
|
|
|
|
const auto cocoaBufferConn =
|
|
QObject::connect(&cockatriceFilter, &UrlSchemeEventFilter::urlReceived,
|
|
[&pendingMacUrls](const QString &url) { pendingMacUrls.append(url); });
|
|
|
|
app.installEventFilter(&cockatriceFilter);
|
|
#endif
|
|
|
|
QObject::connect(&app, &QApplication::lastWindowClosed, &app, &QApplication::quit);
|
|
|
|
qInstallMessageHandler(CockatriceLogger);
|
|
|
|
#ifdef Q_OS_WIN
|
|
app.addLibraryPath(app.applicationDirPath() + "/plugins");
|
|
#endif
|
|
|
|
// These values are only used by the settings loader/saver
|
|
// Wrong or outdated values are kept to not break things
|
|
QCoreApplication::setOrganizationName("Cockatrice");
|
|
QCoreApplication::setOrganizationDomain("cockatrice.github.io");
|
|
QCoreApplication::setApplicationName("Cockatrice");
|
|
QCoreApplication::setApplicationVersion(VERSION_STRING);
|
|
|
|
#ifdef Q_OS_MAC
|
|
qApp->setAttribute(Qt::AA_DontShowIconsInMenus, true);
|
|
#endif
|
|
|
|
// Crash reporting (increment 1): init the SDK early so a crash anywhere below is captured into the
|
|
// local database. Uploads stay consent-gated (require_user_consent, armed in CrashReporter::init):
|
|
// AlwaysSend gives consent here so cached envelopes flush at startup; any other mode starts
|
|
// unconsented and the dialog below drives give/revoke. An empty DSN skips the SDK entirely and
|
|
// the mock fallback further below behaves exactly as before.
|
|
CrashReporter crashReporter;
|
|
const QString crashDatabasePath = SettingsCache::instance().getSettingsPath() + QStringLiteral("sentry");
|
|
{
|
|
QString crashDsn = QString::fromUtf8(qgetenv("COCKATRICE_SENTRY_DSN"));
|
|
if (crashDsn.isEmpty()) {
|
|
crashDsn = QString::fromLatin1(COCKATRICE_SENTRY_DSN);
|
|
}
|
|
crashReporter.init(crashDsn, QString::fromLatin1(VERSION_STRING), crashDatabasePath,
|
|
SettingsCache::instance().personal().getCrashReportingMode());
|
|
if (crashReporter.isReady() && SettingsCache::instance().personal().getCrashReportingMode() ==
|
|
PersonalSettings::CrashReportingMode::AlwaysSend) {
|
|
// Silent opt-in: cached crash envelopes flush automatically once consent is given.
|
|
crashReporter.giveConsent();
|
|
}
|
|
// Mid-session mode changes must reach the SDK: without this, switching from AlwaysSend to
|
|
// NeverSend (or Ask) leaves consent given until restart, so a later crash in this session
|
|
// would still upload after the user opted out. crashReporter outlives main(), so capturing
|
|
// it here is safe.
|
|
QObject::connect(&SettingsCache::instance().personal(), &PersonalSettings::crashReportingModeChanged,
|
|
[&crashReporter](PersonalSettings::CrashReportingMode mode) {
|
|
if (mode == PersonalSettings::CrashReportingMode::AlwaysSend) {
|
|
crashReporter.giveConsent();
|
|
} else {
|
|
crashReporter.revokeConsent();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Persistent session log: keeps this run's logs on disk so the next-startup crash dialog can
|
|
// attach the crashed session's tail (the in-memory buffer is lost on crash). Opened in Append
|
|
// mode with a per-launch session marker; read back via Logger::getPreviousSessionTail().
|
|
const QString persistentLogPath =
|
|
SettingsCache::instance().getSettingsPath() + QStringLiteral("logs/cockatrice.log");
|
|
Logger::getInstance().setPersistentLogPath(persistentLogPath);
|
|
|
|
// Translations
|
|
#ifdef Q_OS_MAC
|
|
translationPath = qApp->applicationDirPath() + "/../Resources/translations";
|
|
#elif defined(Q_OS_WIN)
|
|
translationPath = qApp->applicationDirPath() + "/translations";
|
|
#else // linux
|
|
translationPath = qApp->applicationDirPath() + "/../share/cockatrice/translations";
|
|
#endif
|
|
|
|
// Command-line parser
|
|
QCommandLineParser parser;
|
|
parser.setApplicationDescription("Cockatrice Client");
|
|
parser.addHelpOption();
|
|
parser.addVersionOption();
|
|
|
|
parser.addOptions(
|
|
{{{"c", "connect"}, QCoreApplication::translate("main", "Connect on startup"), "user:pass@host:port"},
|
|
{{"d", "debug-output"}, QCoreApplication::translate("main", "Debug to file")},
|
|
{"simulate-crash",
|
|
QCoreApplication::translate("main", "Show the mock crash-report dialog (nothing is sent)")}});
|
|
|
|
parser.process(app);
|
|
|
|
if (parser.isSet("debug-output")) {
|
|
Logger::getInstance().logToFile(true);
|
|
}
|
|
|
|
// --- Handle files or URLs passed at startup ---
|
|
// Only positional arguments are treated as files/URLs, so options like
|
|
// --connect are never handed off to another instance.
|
|
const QStringList startupFiles = parser.positionalArguments();
|
|
const bool hasActivationFiles = !startupFiles.isEmpty();
|
|
|
|
SingleInstanceManager instance;
|
|
|
|
if (hasActivationFiles) {
|
|
QStringList redactedFiles;
|
|
redactedFiles.reserve(startupFiles.size());
|
|
for (const QString &file : startupFiles) {
|
|
redactedFiles.append(redactActivationUrl(file));
|
|
}
|
|
qCInfo(MainLog) << "Activation launch, files:" << redactedFiles;
|
|
// Activation launch: hand off to the primary instance if one is
|
|
// running, otherwise become the primary ourselves. Do this before
|
|
// constructing the main window so a hand-off exits cheaply.
|
|
if (!instance.tryRun(startupFiles)) {
|
|
qInfo() << "Handed off to a running instance, exiting";
|
|
// Sent successfully → exit
|
|
return 0;
|
|
}
|
|
// No primary instance → become server
|
|
qInfo() << "No existing instance found, becoming primary instance";
|
|
} else {
|
|
// Plain launch: if another instance is running, run independently
|
|
// instead of handing off and exiting.
|
|
if (!instance.tryRun(QStringList())) {
|
|
// Another instance is already running → just run independently
|
|
qInfo() << "Another instance exists, running independently";
|
|
} else {
|
|
qInfo() << "No existing instance found, starting server";
|
|
}
|
|
}
|
|
|
|
rng = new RNG_SFMT(CryptoUtil::randomUInt64());
|
|
themeManager = new ThemeManager;
|
|
soundEngine = new SoundEngine;
|
|
|
|
qtTranslator = new QTranslator;
|
|
translator = new QTranslator;
|
|
installNewTranslator();
|
|
|
|
QLocale::setDefault(QLocale::English);
|
|
|
|
// Dependency Injections
|
|
CardDatabaseManager::setCardPreferenceProvider(new SettingsCardPreferenceProvider());
|
|
CardDatabaseManager::setCardDatabasePathProvider(&SettingsCache::instance());
|
|
CardDatabaseManager::setCardSetPriorityController(&SettingsCache::instance().cardDatabase());
|
|
|
|
qCInfo(MainLog) << "Starting main program";
|
|
|
|
// Front-load the card database before constructing the main window. The
|
|
// binary-cache read is cheap when uncontended (~1s); doing it here -- while no
|
|
// GUI work yet competes for CPU -- avoids the multi-second, GUI-freezing
|
|
// contention that happens when the load runs alongside window construction.
|
|
// The CardDatabaseModel populates from the already-loaded data in its
|
|
// constructor, so the window appears fully populated with no startup lag.
|
|
// Note: checkUnknownSets() is deliberately not run for this front-loaded
|
|
// parse. It would run before MainWindow exists, so
|
|
// cardDatabaseNewSetsFound / cardDatabaseAllNewSetsEnabled would have no
|
|
// receivers, and enabling the sets up-front would suppress the first-run
|
|
// experience MainWindow::startupConfigCheck() and the onboarding wizard
|
|
// provide. Callers that load after startup own the check.
|
|
CardDatabaseManager::getInstance()->loadCardDatabases();
|
|
|
|
MainWindow ui;
|
|
|
|
// A URL launch must own the connection: the intent chain triggered by the
|
|
// URL connects to the server named in the URL, so the window's own startup
|
|
// auto-connect must not race against it (two connectToServer calls tear
|
|
// each other down via doDisconnectFromServer).
|
|
bool hasUrlActivation = std::any_of(startupFiles.begin(), startupFiles.end(), [](const QString &file) {
|
|
return file.startsWith(QStringLiteral("cockatrice://"));
|
|
});
|
|
#ifdef Q_OS_MAC
|
|
// On macOS the launch can arrive through the URL scheme instead of as a
|
|
// positional argument (captured in pendingMacUrls); count those too or the
|
|
// window would auto-connect into the link's own connection attempt.
|
|
hasUrlActivation = hasUrlActivation ||
|
|
std::any_of(pendingMacUrls.cbegin(), pendingMacUrls.cend(),
|
|
[](const QString &url) { return url.startsWith(QStringLiteral("cockatrice://")); });
|
|
#endif
|
|
ui.setSkipStartupAutoConnect(hasUrlActivation);
|
|
|
|
auto handleActivation = [&ui](const QString &file) {
|
|
if (file.startsWith("cockatrice://")) {
|
|
qCInfo(MainLog) << "Handling URL activation:" << redactActivationUrl(file);
|
|
// Route through the window's persistent url parser: it serializes
|
|
// link chains so activations handed over while another chain is
|
|
// still connecting do not connect concurrently.
|
|
ui.handleCockatriceLink(file);
|
|
} else if (QFileInfo(file).exists()) {
|
|
auto openDeckIntent = new IntentOpenLocalDeck(ui.getTabSupervisor(), file);
|
|
QObject::connect(openDeckIntent, &Intent::failed, &ui, [&ui](const QString &reason) {
|
|
QMessageBox::warning(&ui, QObject::tr("Open deck"), reason);
|
|
});
|
|
openDeckIntent->execute();
|
|
}
|
|
};
|
|
|
|
#ifdef Q_OS_MAC
|
|
QObject::disconnect(cocoaBufferConn);
|
|
|
|
QObject::connect(&cockatriceFilter, &UrlSchemeEventFilter::urlReceived,
|
|
[&handleActivation](const QString &url) { handleActivation(url); });
|
|
#endif
|
|
if (parser.isSet("connect")) {
|
|
ui.setConnectTo(parser.value("connect"));
|
|
}
|
|
qCInfo(MainLog) << "MainWindow constructor finished";
|
|
|
|
ui.setWindowIcon(themePixmap(QStringLiteral("cockatrice")));
|
|
// Base name of the desktop file installed next to the binary, without the ".desktop"
|
|
// extension. Wayland reads the window icon from it, and xdg-desktop-portal looks up
|
|
// org.freedesktop.host.portal.Registry by this app ID, so it has to match the file name
|
|
// exactly, down to the case.
|
|
QGuiApplication::setDesktopFileName("cockatrice");
|
|
|
|
SettingsCache::instance().network().setClientID(generateClientID());
|
|
|
|
// If spoiler mode is enabled, we will download the spoilers
|
|
// then reload the DB. otherwise just reload the DB
|
|
SpoilerBackgroundUpdater spoilerBackgroundUpdater;
|
|
|
|
ui.show();
|
|
qCInfo(MainLog) << "ui.show() finished";
|
|
|
|
// MOCK crash-report flow (UI + settings only): no sentry-native upload, no network.
|
|
// Triggered by --simulate-crash or by leftover Windows crash dumps. Handled dumps are deleted
|
|
// after Send / Don't send (or a silent NeverSend skip) so one crash never prompts twice; on
|
|
// non-Windows nothing writes cockatrice.crash.*.dmp yet, so --simulate-crash is the trigger there.
|
|
{
|
|
QString crashDumpDir;
|
|
#ifdef Q_OS_WIN
|
|
// Mirrors the writer (CockatriceUnhandledExceptionFilter): it appends CrashDumps/Cockatrice
|
|
// to %LOCALAPPDATA%, or, when its known-folder lookup fails, to the temp directory walked up
|
|
// two parent_path() steps. %LOCALAPPDATA% is this scan's stand-in for FOLDERID_LocalAppData:
|
|
// both come from the same profile, so when the lookup fails the variable is normally unset.
|
|
QString crashDumpRootPath = QString::fromLocal8Bit(qgetenv("LOCALAPPDATA"));
|
|
if (crashDumpRootPath.isEmpty()) {
|
|
// The writer's fallback expression, verbatim — QDir::cdUp() is existence-checked and
|
|
// would silently leave the path alone here, parent_path() is not.
|
|
const std::filesystem::path fallback = std::filesystem::temp_directory_path().parent_path().parent_path();
|
|
crashDumpRootPath = QString::fromStdWString(fallback.wstring());
|
|
}
|
|
crashDumpDir = QDir(crashDumpRootPath).absoluteFilePath(QStringLiteral("CrashDumps/Cockatrice"));
|
|
#else
|
|
crashDumpDir = QDir::tempPath();
|
|
#endif
|
|
const QStringList crashDumps =
|
|
QDir(crashDumpDir).entryList({QStringLiteral("cockatrice.crash.*.dmp")}, QDir::Files);
|
|
if (parser.isSet("simulate-crash") || !crashDumps.isEmpty()) {
|
|
const PersonalSettings::CrashReportingMode crashMode =
|
|
SettingsCache::instance().personal().getCrashReportingMode();
|
|
if (crashMode == PersonalSettings::CrashReportingMode::AlwaysSend) {
|
|
crashReporter.giveConsent();
|
|
DlgCrashReport::writeMockReport(DlgCrashReport::buildRedactedPreview());
|
|
} else if (crashMode == PersonalSettings::CrashReportingMode::Ask) {
|
|
DlgCrashReport crashDialog(&ui);
|
|
if (crashDialog.exec() == QDialog::Accepted) {
|
|
crashReporter.giveConsent();
|
|
} else {
|
|
crashReporter.revokeConsent();
|
|
}
|
|
}
|
|
// NeverSend: skip silently (no dialog, nothing written or sent).
|
|
for (const QString &dump : crashDumps) {
|
|
QFile::remove(crashDumpDir + QStringLiteral("/") + dump);
|
|
}
|
|
}
|
|
}
|
|
|
|
// force shortcuts to be shown/hidden in right-click menus, regardless of system defaults
|
|
qApp->setAttribute(Qt::AA_DontShowShortcutsInContextMenus,
|
|
!SettingsCache::instance().userInterface().getShowShortcuts());
|
|
|
|
#ifdef Q_OS_MAC
|
|
for (const QString &url : pendingMacUrls) {
|
|
handleActivation(url);
|
|
}
|
|
pendingMacUrls.clear();
|
|
#endif
|
|
|
|
for (const QString &file : startupFiles) {
|
|
handleActivation(file);
|
|
}
|
|
|
|
// Connect to future file/URL events from other instances
|
|
QObject::connect(&instance, &SingleInstanceManager::filesReceived, [&handleActivation](const QStringList &files) {
|
|
for (const QString &file : files) {
|
|
handleActivation(file);
|
|
}
|
|
});
|
|
|
|
int ret = app.exec();
|
|
|
|
crashReporter.shutdown();
|
|
|
|
qCInfo(MainLog) << "Event loop finished, terminating...";
|
|
delete rng;
|
|
PingPixmapGenerator::clear();
|
|
CountryPixmapGenerator::clear();
|
|
UserLevelPixmapGenerator::clear();
|
|
ManaSymbolPixmapGenerator::clear();
|
|
|
|
return ret;
|
|
}
|