|
|
@@ -558,6 +558,7 @@ OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new
|
|
|
connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); });
|
|
|
|
|
|
connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); });
|
|
|
+ connect(App(), &OBSApp::aboutToQuit, this, &OBSBasic::closeWindow);
|
|
|
|
|
|
QActionGroup *actionGroup = new QActionGroup(this);
|
|
|
actionGroup->addAction(ui->actionSceneListMode);
|
|
|
@@ -1372,8 +1373,8 @@ void OBSBasic::OnFirstLoad()
|
|
|
|
|
|
OBSBasic::~OBSBasic()
|
|
|
{
|
|
|
- if (!handledShutdown) {
|
|
|
- applicationShutdown();
|
|
|
+ if (!isClosing()) {
|
|
|
+ closeWindow();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1457,19 +1458,6 @@ void OBSBasic::applicationShutdown() noexcept
|
|
|
* expect or want it to. */
|
|
|
QApplication::sendPostedEvents(nullptr);
|
|
|
|
|
|
- config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER);
|
|
|
- config_save_safe(App()->GetAppConfig(), "tmp", nullptr);
|
|
|
-
|
|
|
- config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled);
|
|
|
- config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked());
|
|
|
- config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode);
|
|
|
- config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode);
|
|
|
- config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode);
|
|
|
- config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode());
|
|
|
- config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked());
|
|
|
- config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked());
|
|
|
- config_save_safe(App()->GetUserConfig(), "tmp", nullptr);
|
|
|
-
|
|
|
#ifdef BROWSER_AVAILABLE
|
|
|
DestroyPanelCookieManager();
|
|
|
delete cef;
|
|
|
@@ -1658,127 +1646,61 @@ bool OBSBasic::ResetAudio()
|
|
|
return obs_reset_audio2(&ai);
|
|
|
}
|
|
|
|
|
|
-void OBSBasic::closeEvent(QCloseEvent *event)
|
|
|
+void OBSBasic::close()
|
|
|
{
|
|
|
- /* Wait for multitrack video stream to start/finish processing in the background */
|
|
|
- if (setupStreamingGuard.valid() &&
|
|
|
- setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) {
|
|
|
- QTimer::singleShot(1000, this, &OBSBasic::close);
|
|
|
- event->ignore();
|
|
|
+ if (isClosePromptOpen() || isClosing()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- /* Do not close window if inside of a temporary event loop because we
|
|
|
- * could be inside of an Auth::LoadUI call. Keep trying once per
|
|
|
- * second until we've exit any known sub-loops. */
|
|
|
- if (os_atomic_load_long(&insideEventLoop) != 0) {
|
|
|
- QTimer::singleShot(1000, this, &OBSBasic::close);
|
|
|
- event->ignore();
|
|
|
- return;
|
|
|
- }
|
|
|
+ OBSMainWindow::close();
|
|
|
+}
|
|
|
|
|
|
-#ifdef YOUTUBE_ENABLED
|
|
|
- /* Also don't close the window if the youtube stream check is active */
|
|
|
- if (youtubeStreamCheckThread) {
|
|
|
- QTimer::singleShot(1000, this, &OBSBasic::close);
|
|
|
- event->ignore();
|
|
|
+void OBSBasic::closeEvent(QCloseEvent *event)
|
|
|
+{
|
|
|
+ if (isClosePromptOpen() || isClosing()) {
|
|
|
return;
|
|
|
}
|
|
|
-#endif
|
|
|
|
|
|
- if (isVisible())
|
|
|
+ if (isVisible()) {
|
|
|
config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry",
|
|
|
saveGeometry().toBase64().constData());
|
|
|
+ }
|
|
|
|
|
|
- bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit");
|
|
|
-
|
|
|
- if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) {
|
|
|
- SetShowing(true);
|
|
|
-
|
|
|
- QMessageBox::StandardButton button =
|
|
|
- OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text"));
|
|
|
+ if (!isReadyToClose()) {
|
|
|
+ event->ignore();
|
|
|
|
|
|
- if (button == QMessageBox::No) {
|
|
|
- event->ignore();
|
|
|
- restart = false;
|
|
|
- return;
|
|
|
- }
|
|
|
+ QTimer::singleShot(1000, this, &OBSBasic::close);
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
if (remux && !remux->close()) {
|
|
|
event->ignore();
|
|
|
restart = false;
|
|
|
- return;
|
|
|
- }
|
|
|
|
|
|
- QWidget::closeEvent(event);
|
|
|
- if (!event->isAccepted())
|
|
|
return;
|
|
|
-
|
|
|
- blog(LOG_INFO, SHUTDOWN_SEPARATOR);
|
|
|
-
|
|
|
- closing = true;
|
|
|
-
|
|
|
- /* While closing, a resize event to OBSQTDisplay could be triggered.
|
|
|
- * The graphics thread on macOS dispatches a lambda function to be
|
|
|
- * executed asynchronously in the main thread. However, the display is
|
|
|
- * sometimes deleted before the lambda function is actually executed.
|
|
|
- * To avoid such a case, destroy displays earlier than others such as
|
|
|
- * deleting browser docks. */
|
|
|
- ui->preview->DestroyDisplay();
|
|
|
- if (program)
|
|
|
- program->DestroyDisplay();
|
|
|
-
|
|
|
- if (outputHandler->VirtualCamActive())
|
|
|
- outputHandler->StopVirtualCam();
|
|
|
-
|
|
|
- if (introCheckThread)
|
|
|
- introCheckThread->wait();
|
|
|
- if (whatsNewInitThread)
|
|
|
- whatsNewInitThread->wait();
|
|
|
- if (updateCheckThread)
|
|
|
- updateCheckThread->wait();
|
|
|
- if (logUploadThread)
|
|
|
- logUploadThread->wait();
|
|
|
- if (devicePropertiesThread && devicePropertiesThread->isRunning()) {
|
|
|
- devicePropertiesThread->wait();
|
|
|
- devicePropertiesThread.reset();
|
|
|
}
|
|
|
|
|
|
- QApplication::sendPostedEvents(nullptr);
|
|
|
-
|
|
|
- signalHandlers.clear();
|
|
|
-
|
|
|
- Auth::Save();
|
|
|
- SaveProjectNow();
|
|
|
- auth.reset();
|
|
|
-
|
|
|
- delete extraBrowsers;
|
|
|
-
|
|
|
- config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData());
|
|
|
-
|
|
|
-#ifdef BROWSER_AVAILABLE
|
|
|
- if (cef)
|
|
|
- SaveExtraBrowserDocks();
|
|
|
-
|
|
|
- ClearExtraBrowserDocks();
|
|
|
-#endif
|
|
|
-
|
|
|
- OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN);
|
|
|
+ if (shouldPromptForClose()) {
|
|
|
+ event->ignore();
|
|
|
+ restart = false;
|
|
|
|
|
|
- disableSaving++;
|
|
|
+ if (!isClosePromptOpen()) {
|
|
|
+ bool shouldClose = promptToClose();
|
|
|
|
|
|
- /* Clear all scene data (dialogs, widgets, widget sub-items, scenes,
|
|
|
- * sources, etc) so that all references are released before shutdown */
|
|
|
- ClearSceneData();
|
|
|
+ if (shouldClose) {
|
|
|
+ closeWindow();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- OnEvent(OBS_FRONTEND_EVENT_EXIT);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- // Destroys the frontend API so plugins can't continue calling it
|
|
|
- obs_frontend_set_callbacks_internal(nullptr);
|
|
|
- api = nullptr;
|
|
|
+ QWidget::closeEvent(event);
|
|
|
+ if (!event->isAccepted()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection);
|
|
|
+ closeWindow();
|
|
|
}
|
|
|
|
|
|
bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *)
|
|
|
@@ -1899,6 +1821,159 @@ config_t *OBSBasic::Config() const
|
|
|
return activeConfiguration;
|
|
|
}
|
|
|
|
|
|
+void OBSBasic::saveAll()
|
|
|
+{
|
|
|
+ if (isVisible()) {
|
|
|
+ config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry",
|
|
|
+ saveGeometry().toBase64().constData());
|
|
|
+ }
|
|
|
+
|
|
|
+ Auth::Save();
|
|
|
+ SaveProjectNow();
|
|
|
+
|
|
|
+ config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData());
|
|
|
+
|
|
|
+#ifdef BROWSER_AVAILABLE
|
|
|
+ if (cef) {
|
|
|
+ SaveExtraBrowserDocks();
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER);
|
|
|
+ config_save_safe(App()->GetAppConfig(), "tmp", nullptr);
|
|
|
+
|
|
|
+ config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled);
|
|
|
+ config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked());
|
|
|
+ config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode);
|
|
|
+ config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode);
|
|
|
+ config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode);
|
|
|
+ config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode());
|
|
|
+ config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked());
|
|
|
+ config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked());
|
|
|
+ config_save_safe(App()->GetUserConfig(), "tmp", nullptr);
|
|
|
+}
|
|
|
+
|
|
|
+bool OBSBasic::isReadyToClose()
|
|
|
+{
|
|
|
+ /* Wait for multitrack video stream to start/finish processing in the background */
|
|
|
+ if (setupStreamingGuard.valid() &&
|
|
|
+ setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Do not close window if inside of a temporary event loop because we
|
|
|
+ * could be inside of an Auth::LoadUI call. Keep trying once per
|
|
|
+ * second until we've exit any known sub-loops. */
|
|
|
+ if (os_atomic_load_long(&insideEventLoop) != 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+#ifdef YOUTUBE_ENABLED
|
|
|
+ /* Also don't close the window if the youtube stream check is active */
|
|
|
+ if (youtubeStreamCheckThread) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool OBSBasic::shouldPromptForClose()
|
|
|
+{
|
|
|
+ bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit");
|
|
|
+ if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+bool OBSBasic::promptToClose()
|
|
|
+{
|
|
|
+ isClosePromptOpen_ = true;
|
|
|
+
|
|
|
+ SetShowing(true);
|
|
|
+ QMessageBox::StandardButton button =
|
|
|
+ OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text"));
|
|
|
+
|
|
|
+ if (button == QMessageBox::No) {
|
|
|
+ isClosePromptOpen_ = false;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ isClosePromptOpen_ = false;
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+void OBSBasic::closeWindow()
|
|
|
+{
|
|
|
+ if (isClosing()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ blog(LOG_INFO, SHUTDOWN_SEPARATOR);
|
|
|
+
|
|
|
+ isClosing_ = true;
|
|
|
+
|
|
|
+ /* While closing, a resize event to OBSQTDisplay could be triggered.
|
|
|
+ * The graphics thread on macOS dispatches a lambda function to be
|
|
|
+ * executed asynchronously in the main thread. However, the display is
|
|
|
+ * sometimes deleted before the lambda function is actually executed.
|
|
|
+ * To avoid such a case, destroy displays earlier than others such as
|
|
|
+ * deleting browser docks. */
|
|
|
+ ui->preview->DestroyDisplay();
|
|
|
+ if (program)
|
|
|
+ program->DestroyDisplay();
|
|
|
+
|
|
|
+ if (outputHandler->VirtualCamActive())
|
|
|
+ outputHandler->StopVirtualCam();
|
|
|
+
|
|
|
+ if (introCheckThread)
|
|
|
+ introCheckThread->wait();
|
|
|
+ if (whatsNewInitThread)
|
|
|
+ whatsNewInitThread->wait();
|
|
|
+ if (updateCheckThread)
|
|
|
+ updateCheckThread->wait();
|
|
|
+ if (logUploadThread)
|
|
|
+ logUploadThread->wait();
|
|
|
+ if (devicePropertiesThread && devicePropertiesThread->isRunning()) {
|
|
|
+ devicePropertiesThread->wait();
|
|
|
+ devicePropertiesThread.reset();
|
|
|
+ }
|
|
|
+
|
|
|
+ QApplication::sendPostedEvents(nullptr);
|
|
|
+
|
|
|
+ signalHandlers.clear();
|
|
|
+ delete extraBrowsers;
|
|
|
+
|
|
|
+ saveAll();
|
|
|
+
|
|
|
+ auth.reset();
|
|
|
+
|
|
|
+#ifdef BROWSER_AVAILABLE
|
|
|
+ ClearExtraBrowserDocks();
|
|
|
+#endif
|
|
|
+
|
|
|
+ OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN);
|
|
|
+
|
|
|
+ disableSaving++;
|
|
|
+
|
|
|
+ /* Clear all scene data (dialogs, widgets, widget sub-items, scenes,
|
|
|
+ * sources, etc) so that all references are released before shutdown */
|
|
|
+ ClearSceneData();
|
|
|
+
|
|
|
+ OnEvent(OBS_FRONTEND_EVENT_EXIT);
|
|
|
+
|
|
|
+ // Destroys the frontend API so plugins can't continue calling it
|
|
|
+ obs_frontend_set_callbacks_internal(nullptr);
|
|
|
+ api = nullptr;
|
|
|
+
|
|
|
+ applicationShutdown();
|
|
|
+ deleteLater();
|
|
|
+
|
|
|
+ QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection);
|
|
|
+}
|
|
|
+
|
|
|
void OBSBasic::UpdateEditMenu()
|
|
|
{
|
|
|
QModelIndexList items = GetAllSelectedSourceItems();
|