1818#include " OBSApp.hpp"
1919
2020#include < components/Multiview.hpp>
21+ #include < dialogs/LogUploadDialog.hpp>
22+ #include < utility/CrashHandler.hpp>
2123#include < utility/OBSEventFilter.hpp>
2224#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
2325#include < utility/models/branches.hpp>
2931#endif
3032#include < qt-wrappers.hpp>
3133
34+ #include < QCheckBox>
35+ #include < QDesktopServices>
3236#if defined(_WIN32) || defined(ENABLE_SPARKLE_UPDATER)
3337#include < QFile>
3438#endif
4246#include < qpa/qplatformnativeinterface.h>
4347#endif
4448
49+ #include < chrono>
50+
4551#ifdef _WIN32
4652#include < sstream>
4753#define WIN32_LEAN_AND_MEAN
@@ -61,6 +67,7 @@ string lastCrashLogFile;
6167
6268extern bool portable_mode;
6369extern bool safe_mode;
70+ extern bool multi;
6471extern bool disable_3p_plugins;
6572extern bool opt_disable_updater;
6673extern bool opt_disable_missing_files_check;
@@ -79,6 +86,67 @@ extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 1;
7986extern " C" __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1 ;
8087#endif
8188
89+ namespace {
90+
91+ typedef struct UncleanLaunchAction {
92+ bool useSafeMode = false ;
93+ bool sendCrashReport = false ;
94+
95+ } UncleanLaunchAction;
96+
97+ UncleanLaunchAction handleUncleanShutdown (bool enableCrashUpload)
98+ {
99+ UncleanLaunchAction launchAction;
100+
101+ blog (LOG_WARNING, " Crash or unclean shutdown detected" );
102+
103+ QMessageBox crashWarning;
104+
105+ crashWarning.setIcon (QMessageBox::Warning);
106+ #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
107+ crashWarning.setOption (QMessageBox::Option::DontUseNativeDialog);
108+ #endif
109+ crashWarning.setWindowTitle (QTStr (" CrashHandling.Dialog.Title" ));
110+ crashWarning.setText (QTStr (" CrashHandling.Dialog.Text" ));
111+
112+ if (enableCrashUpload) {
113+ crashWarning.setInformativeText (QTStr (" CrashHandling.Dialog.PrivacyNotice" ));
114+
115+ QCheckBox *sendCrashReportCheckbox = new QCheckBox (QTStr (" CrashHandling.Dialog.SendReport" ));
116+ crashWarning.setCheckBox (sendCrashReportCheckbox);
117+ }
118+
119+ QPushButton *launchSafeButton =
120+ crashWarning.addButton (QTStr (" CrashHandling.Dialog.LaunchSafe" ), QMessageBox::AcceptRole);
121+ QPushButton *launchNormalButton =
122+ crashWarning.addButton (QTStr (" CrashHandling.Dialog.LaunchNormal" ), QMessageBox::RejectRole);
123+
124+ crashWarning.setDefaultButton (launchNormalButton);
125+
126+ crashWarning.exec ();
127+
128+ bool useSafeMode = crashWarning.clickedButton () == launchSafeButton;
129+
130+ if (useSafeMode) {
131+ launchAction.useSafeMode = true ;
132+
133+ blog (LOG_INFO, " [Safe Mode] Safe mode launch selected, loading 3rd party plugins is disabled" );
134+ } else {
135+ blog (LOG_WARNING, " [Safe Mode] Normal launch selected, loading 3rd party plugins is enabled" );
136+ }
137+
138+ bool sendCrashReport = (enableCrashUpload) ? crashWarning.checkBox ()->isChecked () : false ;
139+
140+ if (sendCrashReport) {
141+ launchAction.sendCrashReport = true ;
142+
143+ blog (LOG_INFO, " User selected to send crash report" );
144+ }
145+
146+ return launchAction;
147+ }
148+ } // namespace
149+
82150QObject *CreateShortcutFilter ()
83151{
84152 return new OBSEventFilter ([](QObject *obj, QEvent *event) {
@@ -751,8 +819,11 @@ std::vector<UpdateBranch> OBSApp::GetBranches()
751819
752820OBSApp::OBSApp (int &argc, char **argv, profiler_name_store_t *store)
753821 : QApplication(argc, argv),
754- profilerNameStore(store)
822+ profilerNameStore(store),
823+ appLaunchUUID_(QUuid::createUuid())
755824{
825+ connect (this , &QCoreApplication::aboutToQuit, this , &OBSApp::applicationShutdown);
826+
756827 /* fix float handling */
757828#if defined(Q_OS_UNIX)
758829 if (!setlocale (LC_NUMERIC, " C" ))
@@ -767,6 +838,11 @@ OBSApp::OBSApp(int &argc, char **argv, profiler_name_store_t *store)
767838#else
768839 connect (qApp, &QGuiApplication::commitDataRequest, this , &OBSApp::commitData);
769840#endif
841+ if (multi) {
842+ crashHandler_ = std::make_unique<OBS::CrashHandler>();
843+ } else {
844+ crashHandler_ = std::make_unique<OBS::CrashHandler>(appLaunchUUID_);
845+ }
770846
771847 sleepInhibitor = os_inhibit_sleep_create (" OBS Video/audio" );
772848
@@ -777,31 +853,7 @@ OBSApp::OBSApp(int &argc, char **argv, profiler_name_store_t *store)
777853 setDesktopFileName (" com.obsproject.Studio" );
778854}
779855
780- OBSApp::~OBSApp ()
781- {
782- #ifdef _WIN32
783- bool disableAudioDucking = config_get_bool (appConfig, " Audio" , " DisableAudioDucking" );
784- if (disableAudioDucking)
785- DisableAudioDucking (false );
786- #else
787- delete snInt;
788- close (sigintFd[0 ]);
789- close (sigintFd[1 ]);
790- #endif
791-
792- #ifdef __APPLE__
793- bool vsyncDisabled = config_get_bool (appConfig, " Video" , " DisableOSXVSync" );
794- bool resetVSync = config_get_bool (appConfig, " Video" , " ResetOSXVSyncOnExit" );
795- if (vsyncDisabled && resetVSync)
796- EnableOSXVSync (true );
797- #endif
798-
799- os_inhibit_sleep_set_active (sleepInhibitor, false );
800- os_inhibit_sleep_destroy (sleepInhibitor);
801-
802- if (libobs_initialized)
803- obs_shutdown ();
804- }
856+ OBSApp::~OBSApp (){};
805857
806858static void move_basic_to_profiles (void )
807859{
@@ -958,6 +1010,22 @@ void OBSApp::AppInit()
9581010 throw " Failed to create profile directories" ;
9591011}
9601012
1013+ void OBSApp::checkForUncleanShutdown ()
1014+ {
1015+ bool hasUncleanShutdown = crashHandler_->hasUncleanShutdown ();
1016+ bool hasNewCrashLog = crashHandler_->hasNewCrashLog ();
1017+
1018+ if (hasUncleanShutdown) {
1019+ UncleanLaunchAction launchAction = handleUncleanShutdown (hasNewCrashLog);
1020+
1021+ safe_mode = launchAction.useSafeMode ;
1022+
1023+ if (launchAction.sendCrashReport ) {
1024+ crashHandler_->uploadLastCrashLog ();
1025+ }
1026+ }
1027+ }
1028+
9611029const char *OBSApp::GetRenderModule () const
9621030{
9631031 const char *renderer = config_get_string (appConfig, " Video" , " Renderer" );
@@ -1090,6 +1158,15 @@ bool OBSApp::OBSInit()
10901158 connect (this , &QGuiApplication::applicationStateChanged,
10911159 [this ](Qt::ApplicationState state) { ResetHotkeyState (state == Qt::ApplicationActive); });
10921160 ResetHotkeyState (applicationState () == Qt::ApplicationActive);
1161+
1162+ connect (crashHandler_.get (), &OBS::CrashHandler::crashLogUploadFailed, this ,
1163+ [this ](const QString &errorMessage) {
1164+ emit this ->logUploadFailed (OBS::LogFileType::CrashLog, errorMessage);
1165+ });
1166+
1167+ connect (crashHandler_.get (), &OBS::CrashHandler::crashLogUploadFinished, this ,
1168+ [this ](const QString &fileUrl) { emit this ->logUploadFinished (OBS::LogFileType::CrashLog, fileUrl); });
1169+
10931170 return true ;
10941171}
10951172
@@ -1173,9 +1250,37 @@ const char *OBSApp::GetCurrentLog() const
11731250 return currentLogFile.c_str ();
11741251}
11751252
1176- const char *OBSApp::GetLastCrashLog () const
1253+ void OBSApp::openCrashLogDirectory () const
1254+ {
1255+ std::filesystem::path crashLogDirectory = crashHandler_->getCrashLogDirectory ();
1256+
1257+ if (crashLogDirectory.empty ()) {
1258+ return ;
1259+ }
1260+
1261+ QString crashLogDirectoryString = QString::fromStdString (crashLogDirectory.u8string ());
1262+
1263+ QUrl crashLogDirectoryURL = QUrl::fromLocalFile (crashLogDirectoryString);
1264+ QDesktopServices::openUrl (crashLogDirectoryURL);
1265+ }
1266+
1267+ void OBSApp::uploadLastAppLog () const
1268+ {
1269+ OBSBasic *basicWindow = static_cast <OBSBasic *>(GetMainWindow ());
1270+
1271+ basicWindow->UploadLog (" obs-studio/logs" , GetLastLog (), OBS::LogFileType::LastAppLog);
1272+ }
1273+
1274+ void OBSApp::uploadCurrentAppLog () const
1275+ {
1276+ OBSBasic *basicWindow = static_cast <OBSBasic *>(GetMainWindow ());
1277+
1278+ basicWindow->UploadLog (" obs-studio/logs" , GetCurrentLog (), OBS::LogFileType::CurrentAppLog);
1279+ }
1280+
1281+ void OBSApp::uploadLastCrashLog ()
11771282{
1178- return lastCrashLogFile. c_str ();
1283+ crashHandler_-> uploadLastCrashLog ();
11791284}
11801285
11811286OBS::LogFileState OBSApp::getLogFileState (OBS::LogFileType type) const
@@ -1589,3 +1694,30 @@ void OBSApp::commitData(QSessionManager &manager)
15891694 }
15901695}
15911696#endif
1697+
1698+ void OBSApp::applicationShutdown () noexcept
1699+ {
1700+ #ifdef _WIN32
1701+ bool disableAudioDucking = config_get_bool (appConfig, " Audio" , " DisableAudioDucking" );
1702+ if (disableAudioDucking)
1703+ DisableAudioDucking (false );
1704+ #else
1705+ delete snInt;
1706+ close (sigintFd[0 ]);
1707+ close (sigintFd[1 ]);
1708+ #endif
1709+
1710+ #ifdef __APPLE__
1711+ bool vsyncDisabled = config_get_bool (appConfig, " Video" , " DisableOSXVSync" );
1712+ bool resetVSync = config_get_bool (appConfig, " Video" , " ResetOSXVSyncOnExit" );
1713+ if (vsyncDisabled && resetVSync)
1714+ EnableOSXVSync (true );
1715+ #endif
1716+
1717+ os_inhibit_sleep_set_active (sleepInhibitor, false );
1718+ os_inhibit_sleep_destroy (sleepInhibitor);
1719+
1720+ if (libobs_initialized) {
1721+ obs_shutdown ();
1722+ }
1723+ }
0 commit comments