diff --git a/Source/Core/DolphinQt2/Main.cpp b/Source/Core/DolphinQt2/Main.cpp index 95593ebe34..c3c62ca06f 100644 --- a/Source/Core/DolphinQt2/Main.cpp +++ b/Source/Core/DolphinQt2/Main.cpp @@ -34,7 +34,7 @@ static bool QtMsgAlertHandler(const char* caption, const char* text, bool yes_no, MsgType style) { - return RunOnObject(QApplication::instance(), [&] { + std::optional r = RunOnObject(QApplication::instance(), [&] { QMessageBox message_box(QApplication::activeWindow()); message_box.setWindowTitle(QString::fromUtf8(caption)); message_box.setText(QString::fromUtf8(text)); @@ -68,6 +68,9 @@ static bool QtMsgAlertHandler(const char* caption, const char* text, bool yes_no return false; }); + if (r.has_value()) + return *r; + return false; } // N.B. On Windows, this should be called from WinMain. Link against qtmain and specify diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index 78ac8725e5..90bf1b2d20 100644 --- a/Source/Core/DolphinQt2/MainWindow.cpp +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -1196,13 +1196,16 @@ void MainWindow::OnImportNANDBackup() }); }, [this] { - return RunOnObject(this, [this] { + std::optional keys_file = RunOnObject(this, [this] { return QFileDialog::getOpenFileName(this, tr("Select the keys file (OTP/SEEPROM dump)"), QDir::currentPath(), tr("BootMii keys file (*.bin);;" "All Files (*)")) .toStdString(); }); + if (keys_file) + return *keys_file; + return std::string(""); }); QueueOnObject(dialog, &QProgressDialog::close); }); diff --git a/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp index 8dcd4df9db..e6d7c29800 100644 --- a/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp @@ -580,12 +580,15 @@ void NetPlayDialog::OnTraversalError(TraversalClient::FailureReason error) bool NetPlayDialog::IsRecording() { - return RunOnObject(m_record_input_box, &QCheckBox::isChecked); + std::optional is_recording = RunOnObject(m_record_input_box, &QCheckBox::isChecked); + if (is_recording) + return *is_recording; + return false; } std::string NetPlayDialog::FindGame(const std::string& game) { - return RunOnObject(this, [this, game] { + std::optional path = RunOnObject(this, [this, game] { for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++) { if (m_game_list_model->GetUniqueIdentifier(i).toStdString() == game) @@ -593,6 +596,9 @@ std::string NetPlayDialog::FindGame(const std::string& game) } return std::string(""); }); + if (path) + return *path; + return std::string(""); } void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier) diff --git a/Source/Core/DolphinQt2/QtUtils/RunOnObject.h b/Source/Core/DolphinQt2/QtUtils/RunOnObject.h index bd0b2ca556..2b016db3f3 100644 --- a/Source/Core/DolphinQt2/QtUtils/RunOnObject.h +++ b/Source/Core/DolphinQt2/QtUtils/RunOnObject.h @@ -4,7 +4,11 @@ #pragma once +#include +#include +#include #include +#include #include #include @@ -15,21 +19,53 @@ class QObject; // QWidget and subclasses are not thread-safe! This helper takes arbitrary code from any thread, // safely runs it on the appropriate GUI thread, waits for it to finish, and returns the result. +// +// If the target object is destructed before the code gets to run, the QPointer will be nulled and +// the function will return nullopt. template auto RunOnObject(QObject* object, F&& functor) { + using OptionalResultT = std::optional>; + // If we queue up a functor on the current thread, it won't run until we return to the event loop, // which means waiting for it to finish will never complete. Instead, run it immediately. if (object->thread() == QThread::currentThread()) - return functor(); + return OptionalResultT(functor()); - Common::Event event; - std::result_of_t result; - QueueOnObject(object, [&event, &result, functor = std::forward(functor) ] { - result = functor(); - event.Set(); - }); + class FnInvokeEvent : public QEvent + { + public: + FnInvokeEvent(F&& functor, QObject* obj, Common::Event& event, OptionalResultT& result) + : QEvent(QEvent::None), m_func(std::move(functor)), m_obj(obj), m_event(event), + m_result(result) + { + } + + ~FnInvokeEvent() + { + if (m_obj) + { + (*m_result) = m_func(); + } + else + { + // is already nullopt + } + m_event.Set(); + } + + private: + F m_func; + QPointer m_obj; + Common::Event& m_event; + OptionalResultT& m_result; + }; + + Common::Event event{}; + OptionalResultT result = std::nullopt; + QCoreApplication::postEvent(object, + new FnInvokeEvent(std::forward(functor), object, event, result)); event.Wait(); return result; } diff --git a/Source/Core/DolphinQt2/Updater.cpp b/Source/Core/DolphinQt2/Updater.cpp index 918119bcf0..6ef92a6b0b 100644 --- a/Source/Core/DolphinQt2/Updater.cpp +++ b/Source/Core/DolphinQt2/Updater.cpp @@ -30,7 +30,7 @@ void Updater::OnUpdateAvailable(const NewVersionInformation& info) { bool later = false; - int choice = RunOnObject(m_parent, [&] { + std::optional choice = RunOnObject(m_parent, [&] { QDialog* dialog = new QDialog(m_parent); dialog->setWindowTitle(tr("Update available")); dialog->setWindowFlags(dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); @@ -79,7 +79,7 @@ void Updater::OnUpdateAvailable(const NewVersionInformation& info) return dialog->exec(); }); - if (choice == QDialog::Accepted) + if (choice && *choice == QDialog::Accepted) { TriggerUpdate(info, later ? AutoUpdateChecker::RestartMode::NO_RESTART_AFTER_UPDATE : AutoUpdateChecker::RestartMode::RESTART_AFTER_UPDATE);