#include "RunGui.hpp" #include "Application.hpp" #include "common/Args.hpp" #include "common/Modes.hpp" #include "common/NetworkManager.hpp" #include "common/QLogging.hpp" #include "singletons/Paths.hpp" #include "singletons/Resources.hpp" #include "singletons/Settings.hpp" #include "singletons/Updates.hpp" #include "util/CombinePath.hpp" #include "widgets/dialogs/LastRunCrashDialog.hpp" #include #include #include #include #include #include #include #ifdef USEWINSDK # include "util/WindowsHelper.hpp" #endif #ifdef C_USE_BREAKPAD # include #endif #ifdef Q_OS_MAC # include "corefoundation/CFBundle.h" #endif namespace chatterino { namespace { void installCustomPalette() { // borrowed from // https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows auto dark = qApp->palette(); dark.setColor(QPalette::Window, QColor(22, 22, 22)); dark.setColor(QPalette::WindowText, Qt::white); dark.setColor(QPalette::Text, Qt::white); dark.setColor(QPalette::Base, QColor("#333")); dark.setColor(QPalette::AlternateBase, QColor("#444")); dark.setColor(QPalette::ToolTipBase, Qt::white); dark.setColor(QPalette::ToolTipText, Qt::white); dark.setColor(QPalette::Dark, QColor(35, 35, 35)); dark.setColor(QPalette::Shadow, QColor(20, 20, 20)); dark.setColor(QPalette::Button, QColor(70, 70, 70)); dark.setColor(QPalette::ButtonText, Qt::white); dark.setColor(QPalette::BrightText, Qt::red); dark.setColor(QPalette::Link, QColor(42, 130, 218)); dark.setColor(QPalette::Highlight, QColor(42, 130, 218)); dark.setColor(QPalette::HighlightedText, Qt::white); dark.setColor(QPalette::PlaceholderText, QColor(127, 127, 127)); dark.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80)); dark.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127)); dark.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127)); dark.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127)); dark.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127)); qApp->setPalette(dark); } void initQt() { // set up the QApplication flags QApplication::setAttribute(Qt::AA_Use96Dpi, true); #ifdef Q_OS_WIN32 QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true); #endif QApplication::setStyle(QStyleFactory::create("Fusion")); #ifndef Q_OS_MAC QApplication::setWindowIcon(QIcon(":/icon.ico")); #endif #ifdef Q_OS_MAC // On the Mac/Cocoa platform this attribute is enabled by default // We override it to ensure shortcuts show in context menus on that platform QApplication::setAttribute(Qt::AA_DontShowShortcutsInContextMenus, false); #endif installCustomPalette(); } void showLastCrashDialog() { //#ifndef C_DISABLE_CRASH_DIALOG // LastRunCrashDialog dialog; // switch (dialog.exec()) // { // case QDialog::Accepted: // { // }; // break; // default: // { // _exit(0); // } // } //#endif } void createRunningFile(const QString &path) { QFile runningFile(path); runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate); runningFile.flush(); runningFile.close(); } void removeRunningFile(const QString &path) { QFile::remove(path); } std::chrono::steady_clock::time_point signalsInitTime; bool restartOnSignal = false; [[noreturn]] void handleSignal(int signum) { using namespace std::chrono_literals; if (restartOnSignal && std::chrono::steady_clock::now() - signalsInitTime > 30s) { QProcess proc; #ifdef Q_OS_MAC // On macOS, programs are bundled into ".app" Application bundles, // when restarting Chatterino that bundle should be opened with the "open" // terminal command instead of directly starting the underlying executable, // as those are 2 different things for the OS and i.e. do not use // the same dock icon (resulting in a second Chatterino icon on restarting) CFURLRef appUrlRef = CFBundleCopyBundleURL(CFBundleGetMainBundle()); CFStringRef macPath = CFURLCopyFileSystemPath(appUrlRef, kCFURLPOSIXPathStyle); const char *pathPtr = CFStringGetCStringPtr(macPath, CFStringGetSystemEncoding()); proc.setProgram("open"); proc.setArguments({pathPtr, "-n", "--args", "--crash-recovery"}); CFRelease(appUrlRef); CFRelease(macPath); #else proc.setProgram(QApplication::applicationFilePath()); proc.setArguments({"--crash-recovery"}); #endif proc.startDetached(); } _exit(signum); } // We want to restart Chatterino when it crashes and the setting is set to // true. void initSignalHandler() { #if defined(NDEBUG) && !defined(CHATTERINO_WITH_CRASHPAD) signalsInitTime = std::chrono::steady_clock::now(); signal(SIGSEGV, handleSignal); #endif } // We delete cache files that haven't been modified in 14 days. This strategy may be // improved in the future. void clearCache(const QDir &dir) { int deletedCount = 0; for (auto &&info : dir.entryInfoList(QDir::Files)) { if (info.lastModified().addDays(14) < QDateTime::currentDateTime()) { bool res = QFile(info.absoluteFilePath()).remove(); if (res) ++deletedCount; } } qCDebug(chatterinoCache) << "Deleted" << deletedCount << "files"; } // We delete all but the five most recent crashdumps. This strategy may be // improved in the future. void clearCrashes(QDir dir) { // crashpad crashdumps are stored inside the Crashes/report directory if (!dir.cd("reports")) { // no reports directory exists = no files to delete return; } dir.setNameFilters({"*.dmp"}); size_t deletedCount = 0; // TODO: use std::views::drop once supported by all compilers size_t filesToSkip = 5; for (auto &&info : dir.entryInfoList(QDir::Files, QDir::Time)) { if (filesToSkip > 0) { filesToSkip--; continue; } if (QFile(info.absoluteFilePath()).remove()) { deletedCount++; } } qCDebug(chatterinoApp) << "Deleted" << deletedCount << "crashdumps"; } } // namespace void runGui(QApplication &a, Paths &paths, Settings &settings) { initQt(); initResources(); initSignalHandler(); settings.restartOnCrash.connect([](const bool &value) { restartOnSignal = value; }); auto thread = std::thread([dir = paths.miscDirectory] { { auto path = combinePath(dir, "Update.exe"); if (QFile::exists(path)) { QFile::remove(path); } } { auto path = combinePath(dir, "update.zip"); if (QFile::exists(path)) { QFile::remove(path); } } }); // Clear the cache 1 minute after start. QTimer::singleShot(60 * 1000, [cachePath = paths.cacheDirectory(), crashDirectory = paths.crashdumpDirectory] { QtConcurrent::run([cachePath]() { clearCache(cachePath); }); QtConcurrent::run([crashDirectory]() { clearCrashes(crashDirectory); }); }); chatterino::NetworkManager::init(); 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); app.initialize(settings, paths); app.run(a); app.save(); removeRunningFile(runningPath); if (!getArgs().dontSaveSettings) { pajlada::Settings::SettingManager::gSave(); } chatterino::NetworkManager::deinit(); #ifdef USEWINSDK // flushing windows clipboard to keep copied messages flushClipboard(); #endif _exit(0); } } // namespace chatterino