2020-08-14 14:12:47 +00:00
|
|
|
|
#include "qthostinterface.h"
|
2020-01-10 03:31:12 +00:00
|
|
|
|
#include "common/assert.h"
|
2020-01-11 03:53:15 +00:00
|
|
|
|
#include "common/audio_stream.h"
|
2020-01-10 03:31:12 +00:00
|
|
|
|
#include "common/byte_stream.h"
|
2020-08-06 12:08:22 +00:00
|
|
|
|
#include "common/file_system.h"
|
2020-01-10 03:31:12 +00:00
|
|
|
|
#include "common/log.h"
|
|
|
|
|
#include "common/string_util.h"
|
2020-09-09 13:44:21 +00:00
|
|
|
|
#include "core/cheats.h"
|
2020-01-02 06:13:03 +00:00
|
|
|
|
#include "core/controller.h"
|
2019-12-31 06:17:17 +00:00
|
|
|
|
#include "core/gpu.h"
|
|
|
|
|
#include "core/system.h"
|
2020-09-01 02:29:22 +00:00
|
|
|
|
#include "frontend-common/game_list.h"
|
2021-02-21 16:38:16 +00:00
|
|
|
|
#include "frontend-common/imgui_fullscreen.h"
|
2020-06-29 16:46:57 +00:00
|
|
|
|
#include "frontend-common/imgui_styles.h"
|
2020-07-13 16:24:11 +00:00
|
|
|
|
#include "frontend-common/ini_settings_interface.h"
|
2020-06-29 16:46:57 +00:00
|
|
|
|
#include "frontend-common/opengl_host_display.h"
|
2020-02-25 13:40:46 +00:00
|
|
|
|
#include "frontend-common/sdl_audio_stream.h"
|
2020-02-15 15:14:53 +00:00
|
|
|
|
#include "frontend-common/sdl_controller_interface.h"
|
2020-06-29 16:46:57 +00:00
|
|
|
|
#include "frontend-common/vulkan_host_display.h"
|
|
|
|
|
#include "imgui.h"
|
2020-03-12 05:32:30 +00:00
|
|
|
|
#include "mainwindow.h"
|
2020-06-29 16:46:57 +00:00
|
|
|
|
#include "qtdisplaywidget.h"
|
2020-03-12 05:32:34 +00:00
|
|
|
|
#include "qtprogresscallback.h"
|
2020-01-02 06:13:03 +00:00
|
|
|
|
#include "qtutils.h"
|
2019-12-31 06:17:17 +00:00
|
|
|
|
#include <QtCore/QCoreApplication>
|
2020-02-15 15:14:04 +00:00
|
|
|
|
#include <QtCore/QDateTime>
|
2020-01-02 06:13:03 +00:00
|
|
|
|
#include <QtCore/QDebug>
|
2020-01-10 03:28:13 +00:00
|
|
|
|
#include <QtCore/QEventLoop>
|
2020-07-28 09:14:44 +00:00
|
|
|
|
#include <QtCore/QFile>
|
2020-02-15 15:14:53 +00:00
|
|
|
|
#include <QtCore/QTimer>
|
2020-07-28 09:14:44 +00:00
|
|
|
|
#include <QtCore/QTranslator>
|
2020-09-09 13:44:21 +00:00
|
|
|
|
#include <QtWidgets/QFileDialog>
|
2020-02-15 15:14:04 +00:00
|
|
|
|
#include <QtWidgets/QMenu>
|
2019-12-31 06:17:17 +00:00
|
|
|
|
#include <QtWidgets/QMessageBox>
|
2020-07-13 16:24:11 +00:00
|
|
|
|
#include <algorithm>
|
2019-12-31 06:17:17 +00:00
|
|
|
|
#include <memory>
|
|
|
|
|
Log_SetChannel(QtHostInterface);
|
|
|
|
|
|
2020-01-07 05:54:37 +00:00
|
|
|
|
#ifdef WIN32
|
2020-08-22 03:01:52 +00:00
|
|
|
|
#include "common/windows_headers.h"
|
2020-06-29 16:46:57 +00:00
|
|
|
|
#include "frontend-common/d3d11_host_display.h"
|
2020-08-22 03:01:52 +00:00
|
|
|
|
#include <KnownFolders.h>
|
|
|
|
|
#include <ShlObj.h>
|
2020-01-07 05:54:37 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2021-02-26 14:44:53 +00:00
|
|
|
|
#ifdef WITH_CHEEVOS
|
|
|
|
|
#include "frontend-common/cheevos.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-04-05 12:59:06 +00:00
|
|
|
|
QtHostInterface::QtHostInterface(QObject* parent) : QObject(parent), CommonHostInterface()
|
2019-12-31 06:17:17 +00:00
|
|
|
|
{
|
2020-09-12 20:01:08 +00:00
|
|
|
|
qRegisterMetaType<std::shared_ptr<const SystemBootParameters>>();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QtHostInterface::~QtHostInterface()
|
|
|
|
|
{
|
2020-06-29 16:46:57 +00:00
|
|
|
|
Assert(!m_display);
|
2019-12-31 06:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-13 12:13:46 +00:00
|
|
|
|
const char* QtHostInterface::GetFrontendName() const
|
|
|
|
|
{
|
|
|
|
|
return "DuckStation Qt Frontend";
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-28 09:14:44 +00:00
|
|
|
|
std::vector<std::pair<QString, QString>> QtHostInterface::getAvailableLanguageList()
|
|
|
|
|
{
|
2020-08-02 03:14:06 +00:00
|
|
|
|
return {{QStringLiteral("English"), QStringLiteral("")},
|
2020-08-03 03:27:20 +00:00
|
|
|
|
{QStringLiteral("Deutsch"), QStringLiteral("de")},
|
2020-08-15 01:37:45 +00:00
|
|
|
|
{QStringLiteral("Español"), QStringLiteral("es")},
|
2020-10-13 14:58:24 +00:00
|
|
|
|
{QStringLiteral("Français"), QStringLiteral("fr")},
|
2020-08-14 14:12:47 +00:00
|
|
|
|
{QStringLiteral("עברית"), QStringLiteral("he")},
|
2020-12-17 00:11:49 +00:00
|
|
|
|
{QStringLiteral("日本語"), QStringLiteral("ja")},
|
2020-08-17 01:54:10 +00:00
|
|
|
|
{QStringLiteral("Italiano"), QStringLiteral("it")},
|
2020-10-08 10:14:56 +00:00
|
|
|
|
{QStringLiteral("Nederlands"), QStringLiteral("nl")},
|
2021-01-04 14:32:41 +00:00
|
|
|
|
{QStringLiteral("Polski"), QStringLiteral("pl")},
|
2020-08-15 01:37:45 +00:00
|
|
|
|
{QStringLiteral("Português (Pt)"), QStringLiteral("pt-pt")},
|
|
|
|
|
{QStringLiteral("Português (Br)"), QStringLiteral("pt-br")},
|
2020-09-22 14:39:57 +00:00
|
|
|
|
{QStringLiteral("Русский"), QStringLiteral("ru")},
|
2020-08-14 20:47:23 +00:00
|
|
|
|
{QStringLiteral("简体中文"), QStringLiteral("zh-cn")}};
|
2020-07-28 09:14:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
|
bool QtHostInterface::Initialize()
|
2020-04-05 12:59:06 +00:00
|
|
|
|
{
|
|
|
|
|
createThread();
|
2020-07-28 09:14:44 +00:00
|
|
|
|
if (!m_worker_thread->waitForInit())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
installTranslator();
|
|
|
|
|
return true;
|
2020-04-05 12:59:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::Shutdown()
|
|
|
|
|
{
|
|
|
|
|
stopThread();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtHostInterface::initializeOnThread()
|
2020-03-21 14:49:46 +00:00
|
|
|
|
{
|
2021-02-21 10:22:44 +00:00
|
|
|
|
SetUserDirectory();
|
|
|
|
|
m_settings_interface = std::make_unique<INISettingsInterface>(GetSettingsFileName());
|
|
|
|
|
|
2020-03-21 14:49:46 +00:00
|
|
|
|
if (!CommonHostInterface::Initialize())
|
|
|
|
|
return false;
|
|
|
|
|
|
2021-02-21 16:38:16 +00:00
|
|
|
|
// imgui setup
|
|
|
|
|
setImGuiFont();
|
|
|
|
|
setImGuiKeyMap();
|
|
|
|
|
|
2020-04-05 12:59:06 +00:00
|
|
|
|
// make sure the controllers have been detected
|
2020-03-21 14:49:46 +00:00
|
|
|
|
if (m_controller_interface)
|
|
|
|
|
m_controller_interface->PollEvents();
|
|
|
|
|
|
2020-04-05 12:59:06 +00:00
|
|
|
|
// bind buttons/axises
|
2020-04-29 03:27:58 +00:00
|
|
|
|
createBackgroundControllerPollTimer();
|
|
|
|
|
startBackgroundControllerPollTimer();
|
2020-03-21 14:49:46 +00:00
|
|
|
|
updateInputMap();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-05 12:59:06 +00:00
|
|
|
|
void QtHostInterface::shutdownOnThread()
|
2020-03-21 14:49:46 +00:00
|
|
|
|
{
|
2020-04-29 03:27:58 +00:00
|
|
|
|
destroyBackgroundControllerPollTimer();
|
2020-03-21 14:49:46 +00:00
|
|
|
|
CommonHostInterface::Shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-28 09:14:44 +00:00
|
|
|
|
void QtHostInterface::installTranslator()
|
|
|
|
|
{
|
|
|
|
|
m_translator = std::make_unique<QTranslator>();
|
|
|
|
|
|
|
|
|
|
std::string language = GetStringSettingValue("Main", "Language", "");
|
|
|
|
|
if (language.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const QString path =
|
|
|
|
|
QStringLiteral("%1/translations/duckstation-qt_%3.qm").arg(qApp->applicationDirPath()).arg(language.c_str());
|
|
|
|
|
if (!QFile::exists(path))
|
|
|
|
|
{
|
|
|
|
|
QMessageBox::warning(
|
|
|
|
|
nullptr, QStringLiteral("Translation Error"),
|
|
|
|
|
QStringLiteral("Failed to find translation file for language '%1':\n%2").arg(language.c_str()).arg(path));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_translator->load(path))
|
|
|
|
|
{
|
|
|
|
|
QMessageBox::warning(
|
|
|
|
|
nullptr, QStringLiteral("Translation Error"),
|
|
|
|
|
QStringLiteral("Failed to load translation file for language '%1':\n%2").arg(language.c_str()).arg(path));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log_InfoPrintf("Loaded translation file for language '%s'", language.c_str());
|
|
|
|
|
qApp->installTranslator(m_translator.get());
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-31 06:17:17 +00:00
|
|
|
|
void QtHostInterface::ReportError(const char* message)
|
|
|
|
|
{
|
2020-01-24 04:51:30 +00:00
|
|
|
|
HostInterface::ReportError(message);
|
|
|
|
|
|
2020-04-05 12:58:47 +00:00
|
|
|
|
const bool was_fullscreen = m_is_fullscreen;
|
|
|
|
|
if (was_fullscreen)
|
|
|
|
|
SetFullscreen(false);
|
|
|
|
|
|
2020-09-14 14:36:43 +00:00
|
|
|
|
emit errorReported(QString::fromUtf8(message));
|
2020-03-12 03:53:58 +00:00
|
|
|
|
|
2020-04-05 12:58:47 +00:00
|
|
|
|
if (was_fullscreen)
|
|
|
|
|
SetFullscreen(true);
|
2019-12-31 06:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::ReportMessage(const char* message)
|
|
|
|
|
{
|
2020-01-24 04:51:30 +00:00
|
|
|
|
HostInterface::ReportMessage(message);
|
|
|
|
|
|
2020-09-14 14:36:43 +00:00
|
|
|
|
emit messageReported(QString::fromUtf8(message));
|
2019-12-31 06:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-16 15:17:26 +00:00
|
|
|
|
void QtHostInterface::ReportDebuggerMessage(const char* message)
|
|
|
|
|
{
|
|
|
|
|
HostInterface::ReportDebuggerMessage(message);
|
|
|
|
|
|
|
|
|
|
emit debuggerMessageReported(QString::fromUtf8(message));
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-26 09:25:57 +00:00
|
|
|
|
bool QtHostInterface::ConfirmMessage(const char* message)
|
|
|
|
|
{
|
2020-04-05 12:58:47 +00:00
|
|
|
|
const bool was_fullscreen = m_is_fullscreen;
|
|
|
|
|
if (was_fullscreen)
|
|
|
|
|
SetFullscreen(false);
|
2020-03-12 03:53:58 +00:00
|
|
|
|
|
2020-09-14 14:36:43 +00:00
|
|
|
|
const bool result = messageConfirmed(QString::fromUtf8(message));
|
2020-03-12 03:53:58 +00:00
|
|
|
|
|
2020-04-05 12:58:47 +00:00
|
|
|
|
if (was_fullscreen)
|
|
|
|
|
SetFullscreen(true);
|
2020-03-12 03:53:58 +00:00
|
|
|
|
|
|
|
|
|
return result;
|
2020-02-26 09:25:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-21 09:49:04 +00:00
|
|
|
|
void QtHostInterface::SetBoolSettingValue(const char* section, const char* key, bool value)
|
2020-07-13 16:24:11 +00:00
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(m_settings_mutex);
|
2020-07-21 09:49:04 +00:00
|
|
|
|
m_settings_interface->SetBoolValue(section, key, value);
|
2020-07-21 09:58:50 +00:00
|
|
|
|
queueSettingsSave();
|
2020-07-13 16:24:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-21 09:49:04 +00:00
|
|
|
|
void QtHostInterface::SetIntSettingValue(const char* section, const char* key, int value)
|
|
|
|
|
{
|
2020-07-13 16:24:11 +00:00
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(m_settings_mutex);
|
2020-07-21 09:49:04 +00:00
|
|
|
|
m_settings_interface->SetIntValue(section, key, value);
|
2020-07-21 09:58:50 +00:00
|
|
|
|
queueSettingsSave();
|
2020-07-21 09:49:04 +00:00
|
|
|
|
}
|
2020-07-13 16:24:11 +00:00
|
|
|
|
|
2020-07-21 09:49:04 +00:00
|
|
|
|
void QtHostInterface::SetFloatSettingValue(const char* section, const char* key, float value)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(m_settings_mutex);
|
|
|
|
|
m_settings_interface->SetFloatValue(section, key, value);
|
2020-07-21 09:58:50 +00:00
|
|
|
|
queueSettingsSave();
|
2020-07-21 09:49:04 +00:00
|
|
|
|
}
|
2020-07-13 16:24:11 +00:00
|
|
|
|
|
2020-07-21 09:49:04 +00:00
|
|
|
|
void QtHostInterface::SetStringSettingValue(const char* section, const char* key, const char* value)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(m_settings_mutex);
|
|
|
|
|
m_settings_interface->SetStringValue(section, key, value);
|
2020-07-21 09:58:50 +00:00
|
|
|
|
queueSettingsSave();
|
2020-07-21 09:49:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::SetStringListSettingValue(const char* section, const char* key,
|
|
|
|
|
const std::vector<std::string>& values)
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(m_settings_mutex);
|
|
|
|
|
m_settings_interface->SetStringList(section, key, values);
|
2020-07-21 09:58:50 +00:00
|
|
|
|
queueSettingsSave();
|
2020-07-13 16:24:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-21 09:49:04 +00:00
|
|
|
|
void QtHostInterface::RemoveSettingValue(const char* section, const char* key)
|
2020-07-13 16:24:11 +00:00
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(m_settings_mutex);
|
|
|
|
|
m_settings_interface->DeleteValue(section, key);
|
2020-07-21 09:58:50 +00:00
|
|
|
|
queueSettingsSave();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::queueSettingsSave()
|
|
|
|
|
{
|
2020-08-11 16:27:06 +00:00
|
|
|
|
if (m_settings_save_timer)
|
|
|
|
|
return;
|
2020-07-21 09:58:50 +00:00
|
|
|
|
|
2020-08-11 16:27:06 +00:00
|
|
|
|
m_settings_save_timer = std::make_unique<QTimer>();
|
|
|
|
|
connect(m_settings_save_timer.get(), &QTimer::timeout, this, &QtHostInterface::doSaveSettings);
|
|
|
|
|
m_settings_save_timer->setSingleShot(true);
|
2020-07-21 09:58:50 +00:00
|
|
|
|
m_settings_save_timer->start(SETTINGS_SAVE_DELAY);
|
2020-08-11 16:27:06 +00:00
|
|
|
|
m_settings_save_timer->moveToThread(m_worker_thread);
|
2020-07-21 09:58:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::doSaveSettings()
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(m_settings_mutex);
|
|
|
|
|
m_settings_interface->Save();
|
|
|
|
|
m_settings_save_timer.reset();
|
2020-01-24 04:49:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-28 07:00:09 +00:00
|
|
|
|
void QtHostInterface::setDefaultSettings()
|
2020-01-01 04:01:58 +00:00
|
|
|
|
{
|
2020-02-28 07:00:09 +00:00
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "setDefaultSettings", Qt::QueuedConnection);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 07:09:18 +00:00
|
|
|
|
Settings old_settings(std::move(g_settings));
|
2020-04-30 15:01:50 +00:00
|
|
|
|
{
|
2020-07-13 16:24:11 +00:00
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(m_settings_mutex);
|
|
|
|
|
SetDefaultSettings(*m_settings_interface.get());
|
2020-07-21 09:58:50 +00:00
|
|
|
|
m_settings_interface->Save();
|
|
|
|
|
|
2020-07-13 16:24:11 +00:00
|
|
|
|
CommonHostInterface::LoadSettings(*m_settings_interface.get());
|
2020-08-20 11:30:11 +00:00
|
|
|
|
CommonHostInterface::ApplyGameSettings(false);
|
2020-08-20 12:25:49 +00:00
|
|
|
|
CommonHostInterface::FixIncompatibleSettings(false);
|
2020-04-30 15:01:50 +00:00
|
|
|
|
}
|
2020-06-08 16:05:55 +00:00
|
|
|
|
|
|
|
|
|
CheckForSettingsChanges(old_settings);
|
2019-12-31 06:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-20 11:30:11 +00:00
|
|
|
|
void QtHostInterface::applySettings(bool display_osd_messages /* = false */)
|
2019-12-31 06:17:17 +00:00
|
|
|
|
{
|
2020-01-24 04:49:49 +00:00
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2020-08-20 11:30:11 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "applySettings", Qt::QueuedConnection, Q_ARG(bool, display_osd_messages));
|
2020-01-24 04:49:49 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-30 05:52:36 +00:00
|
|
|
|
ApplySettings(display_osd_messages);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::ApplySettings(bool display_osd_messages)
|
|
|
|
|
{
|
2021-02-21 10:22:44 +00:00
|
|
|
|
CommonHostInterface::ApplySettings(display_osd_messages);
|
2020-04-05 12:58:47 +00:00
|
|
|
|
|
|
|
|
|
// detect when render-to-main flag changes
|
2020-07-31 07:09:18 +00:00
|
|
|
|
if (!System::IsShutdown())
|
2020-04-05 12:58:47 +00:00
|
|
|
|
{
|
2020-07-13 16:24:11 +00:00
|
|
|
|
const bool render_to_main = m_settings_interface->GetBoolValue("Main", "RenderToMainWindow", true);
|
2020-06-29 16:46:57 +00:00
|
|
|
|
if (m_display && !m_is_fullscreen && render_to_main != m_is_rendering_to_main)
|
2020-04-30 15:00:29 +00:00
|
|
|
|
{
|
|
|
|
|
m_is_rendering_to_main = render_to_main;
|
|
|
|
|
updateDisplayState();
|
|
|
|
|
}
|
2021-02-21 16:38:16 +00:00
|
|
|
|
else if (!m_fullscreen_ui_enabled)
|
2020-04-30 15:00:29 +00:00
|
|
|
|
{
|
|
|
|
|
renderDisplay();
|
|
|
|
|
}
|
2020-04-05 12:58:47 +00:00
|
|
|
|
}
|
2019-12-31 06:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-08 03:37:43 +00:00
|
|
|
|
void QtHostInterface::refreshGameList(bool invalidate_cache /* = false */, bool invalidate_database /* = false */)
|
2019-12-31 06:17:17 +00:00
|
|
|
|
{
|
2020-03-12 05:32:34 +00:00
|
|
|
|
Assert(!isOnWorkerThread());
|
|
|
|
|
|
2020-07-13 16:24:11 +00:00
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(m_settings_mutex);
|
|
|
|
|
m_game_list->SetSearchDirectoriesFromSettings(*m_settings_interface.get());
|
2020-03-12 05:32:34 +00:00
|
|
|
|
|
2021-01-24 08:55:35 +00:00
|
|
|
|
QtProgressCallback progress(m_main_window, invalidate_cache ? 0.0f : 1.0f);
|
2020-03-12 05:32:34 +00:00
|
|
|
|
m_game_list->Refresh(invalidate_cache, invalidate_database, &progress);
|
2019-12-31 06:17:17 +00:00
|
|
|
|
emit gameListRefreshed();
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-12 05:32:30 +00:00
|
|
|
|
void QtHostInterface::setMainWindow(MainWindow* window)
|
|
|
|
|
{
|
|
|
|
|
DebugAssert((!m_main_window && window) || (m_main_window && !window));
|
|
|
|
|
m_main_window = window;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-12 20:01:08 +00:00
|
|
|
|
void QtHostInterface::bootSystem(std::shared_ptr<const SystemBootParameters> params)
|
2020-01-24 04:49:51 +00:00
|
|
|
|
{
|
2020-02-15 15:14:28 +00:00
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2020-09-12 20:01:08 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "bootSystem", Qt::QueuedConnection,
|
|
|
|
|
Q_ARG(std::shared_ptr<const SystemBootParameters>, std::move(params)));
|
2020-02-15 15:14:28 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2020-01-24 04:49:51 +00:00
|
|
|
|
|
2020-06-05 17:44:57 +00:00
|
|
|
|
emit emulationStarting();
|
2020-12-26 14:30:16 +00:00
|
|
|
|
if (!BootSystem(*params))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// force a frame to be drawn to repaint the window
|
|
|
|
|
renderDisplay();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 15:14:49 +00:00
|
|
|
|
void QtHostInterface::resumeSystemFromState(const QString& filename, bool boot_on_failure)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "resumeSystemFromState", Qt::QueuedConnection, Q_ARG(const QString&, filename),
|
|
|
|
|
Q_ARG(bool, boot_on_failure));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-05 17:44:57 +00:00
|
|
|
|
emit emulationStarting();
|
2020-10-02 15:23:04 +00:00
|
|
|
|
if (filename.isEmpty())
|
|
|
|
|
ResumeSystemFromMostRecentState();
|
|
|
|
|
else
|
|
|
|
|
ResumeSystemFromState(filename.toStdString().c_str(), boot_on_failure);
|
2020-02-15 15:14:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-23 05:03:00 +00:00
|
|
|
|
void QtHostInterface::resumeSystemFromMostRecentState()
|
|
|
|
|
{
|
2020-10-02 15:23:04 +00:00
|
|
|
|
std::string state_filename = GetMostRecentResumeSaveStatePath();
|
|
|
|
|
if (state_filename.empty())
|
|
|
|
|
{
|
|
|
|
|
emit errorReported(tr("No resume save state found."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadState(QString::fromStdString(state_filename));
|
2020-05-23 05:03:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-22 11:13:51 +00:00
|
|
|
|
void QtHostInterface::onDisplayWindowKeyEvent(int key, bool pressed)
|
2020-01-02 06:13:03 +00:00
|
|
|
|
{
|
2020-04-22 11:13:51 +00:00
|
|
|
|
DebugAssert(isOnWorkerThread());
|
2021-02-21 16:38:16 +00:00
|
|
|
|
|
|
|
|
|
const u32 masked_key = static_cast<u32>(key) & IMGUI_KEY_MASK;
|
|
|
|
|
if (masked_key < countof(ImGuiIO::KeysDown))
|
2021-03-04 15:13:17 +00:00
|
|
|
|
{
|
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
|
io.KeysDown[masked_key] = pressed;
|
|
|
|
|
if (io.WantCaptureKeyboard)
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-02-21 16:38:16 +00:00
|
|
|
|
|
2021-02-27 16:45:30 +00:00
|
|
|
|
HandleHostKeyEvent(key & ~Qt::KeyboardModifierMask, key & Qt::KeyboardModifierMask, pressed);
|
2020-01-02 06:13:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-26 07:36:49 +00:00
|
|
|
|
void QtHostInterface::onDisplayWindowMouseMoveEvent(int x, int y)
|
|
|
|
|
{
|
2020-04-29 06:51:44 +00:00
|
|
|
|
// display might be null here if the event happened after shutdown
|
2020-04-26 07:36:49 +00:00
|
|
|
|
DebugAssert(isOnWorkerThread());
|
2020-07-12 16:36:20 +00:00
|
|
|
|
if (!m_display)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-02-21 16:38:16 +00:00
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
|
io.MousePos[0] = static_cast<float>(x);
|
|
|
|
|
io.MousePos[1] = static_cast<float>(y);
|
2020-07-12 16:36:20 +00:00
|
|
|
|
|
2021-02-21 16:38:16 +00:00
|
|
|
|
m_display->SetMousePosition(x, y);
|
2020-04-26 07:36:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::onDisplayWindowMouseButtonEvent(int button, bool pressed)
|
2020-04-26 07:23:42 +00:00
|
|
|
|
{
|
|
|
|
|
DebugAssert(isOnWorkerThread());
|
2020-07-12 16:36:20 +00:00
|
|
|
|
|
2020-07-17 03:32:42 +00:00
|
|
|
|
if (ImGui::GetCurrentContext() && (button > 0 && button <= static_cast<int>(countof(ImGuiIO::MouseDown))))
|
2020-07-12 16:36:20 +00:00
|
|
|
|
{
|
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
|
io.MouseDown[button - 1] = pressed;
|
|
|
|
|
|
|
|
|
|
if (io.WantCaptureMouse)
|
|
|
|
|
{
|
|
|
|
|
// don't consume input events if it's hitting the UI instead
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-26 07:23:42 +00:00
|
|
|
|
HandleHostMouseEvent(button, pressed);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-25 16:42:20 +00:00
|
|
|
|
void QtHostInterface::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle)
|
|
|
|
|
{
|
|
|
|
|
DebugAssert(isOnWorkerThread());
|
|
|
|
|
|
|
|
|
|
if (ImGui::GetCurrentContext())
|
|
|
|
|
{
|
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
|
|
|
|
|
|
if (delta_angle.x() > 0)
|
|
|
|
|
io.MouseWheelH += 1.0f;
|
|
|
|
|
else if (delta_angle.x() < 0)
|
|
|
|
|
io.MouseWheelH -= 1.0f;
|
|
|
|
|
|
|
|
|
|
if (delta_angle.y() > 0)
|
|
|
|
|
io.MouseWheel += 1.0f;
|
|
|
|
|
else if (delta_angle.y() < 0)
|
|
|
|
|
io.MouseWheel -= 1.0f;
|
|
|
|
|
|
|
|
|
|
if (io.WantCaptureMouse)
|
|
|
|
|
{
|
|
|
|
|
// don't consume input events if it's hitting the UI instead
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HandleHostMouseWheelEvent(delta_angle.x(), delta_angle.y());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::onDisplayWindowResized(int width, int height)
|
2020-01-02 09:14:16 +00:00
|
|
|
|
{
|
2020-02-15 15:14:56 +00:00
|
|
|
|
// this can be null if it was destroyed and the main thread is late catching up
|
2020-06-29 16:46:57 +00:00
|
|
|
|
if (!m_display)
|
2020-03-18 12:27:45 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2021-02-27 03:53:03 +00:00
|
|
|
|
Log_DevPrintf("Display window resized to %dx%d", width, height);
|
2020-06-29 16:46:57 +00:00
|
|
|
|
m_display->ResizeRenderWindow(width, height);
|
2021-02-21 16:38:16 +00:00
|
|
|
|
OnHostDisplayResized(width, height, m_display->GetWindowScale());
|
2020-03-18 12:27:45 +00:00
|
|
|
|
|
|
|
|
|
// re-render the display, since otherwise it will be out of date and stretched if paused
|
2020-07-31 07:09:18 +00:00
|
|
|
|
if (!System::IsShutdown())
|
2020-08-02 17:06:03 +00:00
|
|
|
|
{
|
2020-11-01 14:38:54 +00:00
|
|
|
|
if (m_is_exclusive_fullscreen && !m_display->IsFullscreen())
|
|
|
|
|
{
|
2021-02-25 02:51:43 +00:00
|
|
|
|
// we lost exclusive fullscreen, switch to borderless
|
|
|
|
|
AddOSDMessage(TranslateStdString("OSDMessage", "Lost exclusive fullscreen."), 10.0f);
|
2020-11-01 14:38:54 +00:00
|
|
|
|
m_is_exclusive_fullscreen = false;
|
|
|
|
|
m_is_fullscreen = false;
|
2021-02-25 02:51:43 +00:00
|
|
|
|
m_lost_exclusive_fullscreen = true;
|
2020-11-01 14:38:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-21 16:38:16 +00:00
|
|
|
|
// force redraw if we're paused
|
|
|
|
|
if (!m_fullscreen_ui_enabled)
|
|
|
|
|
renderDisplay();
|
2020-08-02 17:06:03 +00:00
|
|
|
|
}
|
2020-01-02 09:14:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-25 16:42:20 +00:00
|
|
|
|
void QtHostInterface::onDisplayWindowFocused()
|
2021-02-25 02:51:43 +00:00
|
|
|
|
{
|
|
|
|
|
if (!m_display || !m_lost_exclusive_fullscreen)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// try to restore exclusive fullscreen
|
|
|
|
|
m_lost_exclusive_fullscreen = false;
|
|
|
|
|
m_is_exclusive_fullscreen = true;
|
|
|
|
|
m_is_fullscreen = true;
|
|
|
|
|
updateDisplayState();
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-22 11:50:49 +00:00
|
|
|
|
void QtHostInterface::redrawDisplayWindow()
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "redrawDisplayWindow", Qt::QueuedConnection);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 07:09:18 +00:00
|
|
|
|
if (!m_display || System::IsShutdown())
|
2020-03-22 11:50:49 +00:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
renderDisplay();
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-05 12:58:47 +00:00
|
|
|
|
void QtHostInterface::toggleFullscreen()
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "toggleFullscreen", Qt::QueuedConnection);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-05 12:58:54 +00:00
|
|
|
|
SetFullscreen(!m_is_fullscreen);
|
2020-04-05 12:58:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
|
bool QtHostInterface::AcquireHostDisplay()
|
2020-01-24 04:51:52 +00:00
|
|
|
|
{
|
2020-04-22 11:13:51 +00:00
|
|
|
|
Assert(!m_display);
|
2020-02-15 15:14:28 +00:00
|
|
|
|
|
2020-07-13 16:24:11 +00:00
|
|
|
|
m_is_rendering_to_main = m_settings_interface->GetBoolValue("Main", "RenderToMainWindow", true);
|
2020-04-22 11:13:51 +00:00
|
|
|
|
|
2020-12-26 13:22:24 +00:00
|
|
|
|
QtDisplayWidget* display_widget = createDisplayRequested(m_worker_thread, m_is_fullscreen, m_is_rendering_to_main);
|
2020-06-29 16:46:57 +00:00
|
|
|
|
if (!display_widget || !m_display->HasRenderDevice())
|
2020-01-24 04:51:52 +00:00
|
|
|
|
{
|
2020-04-22 11:13:51 +00:00
|
|
|
|
emit destroyDisplayRequested();
|
2020-11-30 15:17:26 +00:00
|
|
|
|
m_display.reset();
|
2020-02-15 15:14:28 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-01-24 04:51:52 +00:00
|
|
|
|
|
2020-06-29 16:46:57 +00:00
|
|
|
|
if (!m_display->MakeRenderContextCurrent() ||
|
2020-12-26 13:22:24 +00:00
|
|
|
|
!m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device,
|
|
|
|
|
g_settings.gpu_threaded_presentation) ||
|
2021-02-21 16:38:16 +00:00
|
|
|
|
!CreateHostDisplayResources())
|
2020-02-15 15:14:28 +00:00
|
|
|
|
{
|
2021-01-30 04:39:56 +00:00
|
|
|
|
ReleaseHostDisplayResources();
|
2020-06-29 16:46:57 +00:00
|
|
|
|
m_display->DestroyRenderDevice();
|
2020-04-22 11:13:51 +00:00
|
|
|
|
emit destroyDisplayRequested();
|
2020-06-29 16:46:57 +00:00
|
|
|
|
m_display.reset();
|
2020-02-15 15:14:28 +00:00
|
|
|
|
return false;
|
2020-01-24 04:51:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-29 16:46:57 +00:00
|
|
|
|
connectDisplaySignals(display_widget);
|
2020-11-01 14:38:54 +00:00
|
|
|
|
m_is_exclusive_fullscreen = m_display->IsFullscreen();
|
2020-02-15 15:14:28 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2020-01-24 04:51:52 +00:00
|
|
|
|
|
2020-06-29 16:46:57 +00:00
|
|
|
|
HostDisplay* QtHostInterface::createHostDisplay()
|
2020-04-22 11:13:51 +00:00
|
|
|
|
{
|
2020-07-31 07:09:18 +00:00
|
|
|
|
switch (g_settings.gpu_renderer)
|
2020-06-18 14:18:53 +00:00
|
|
|
|
{
|
|
|
|
|
case GPURenderer::HardwareVulkan:
|
2020-06-29 16:46:57 +00:00
|
|
|
|
m_display = std::make_unique<FrontendCommon::VulkanHostDisplay>();
|
2020-06-18 14:18:53 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GPURenderer::HardwareOpenGL:
|
|
|
|
|
#ifndef WIN32
|
|
|
|
|
default:
|
|
|
|
|
#endif
|
2020-06-29 16:46:57 +00:00
|
|
|
|
m_display = std::make_unique<FrontendCommon::OpenGLHostDisplay>();
|
2020-06-18 14:18:53 +00:00
|
|
|
|
break;
|
|
|
|
|
|
2020-04-22 11:13:51 +00:00
|
|
|
|
#ifdef WIN32
|
2020-06-18 14:18:53 +00:00
|
|
|
|
case GPURenderer::HardwareD3D11:
|
|
|
|
|
default:
|
2020-06-29 16:46:57 +00:00
|
|
|
|
m_display = std::make_unique<FrontendCommon::D3D11HostDisplay>();
|
2020-06-18 14:18:53 +00:00
|
|
|
|
break;
|
2020-04-22 11:13:51 +00:00
|
|
|
|
#endif
|
2020-06-18 14:18:53 +00:00
|
|
|
|
}
|
2020-04-22 11:13:51 +00:00
|
|
|
|
|
2020-06-29 16:46:57 +00:00
|
|
|
|
return m_display.get();
|
2020-04-22 11:13:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-29 16:46:57 +00:00
|
|
|
|
void QtHostInterface::connectDisplaySignals(QtDisplayWidget* widget)
|
2020-04-22 11:13:51 +00:00
|
|
|
|
{
|
2020-06-29 16:46:57 +00:00
|
|
|
|
widget->disconnect(this);
|
|
|
|
|
|
2021-02-25 16:42:20 +00:00
|
|
|
|
connect(widget, &QtDisplayWidget::windowFocusEvent, this, &QtHostInterface::onDisplayWindowFocused);
|
|
|
|
|
connect(widget, &QtDisplayWidget::windowResizedEvent, this, &QtHostInterface::onDisplayWindowResized);
|
2020-04-22 11:13:51 +00:00
|
|
|
|
connect(widget, &QtDisplayWidget::windowRestoredEvent, this, &QtHostInterface::redrawDisplayWindow);
|
|
|
|
|
connect(widget, &QtDisplayWidget::windowClosedEvent, this, &QtHostInterface::powerOffSystem,
|
|
|
|
|
Qt::BlockingQueuedConnection);
|
|
|
|
|
connect(widget, &QtDisplayWidget::windowKeyEvent, this, &QtHostInterface::onDisplayWindowKeyEvent);
|
2020-04-26 07:36:49 +00:00
|
|
|
|
connect(widget, &QtDisplayWidget::windowMouseMoveEvent, this, &QtHostInterface::onDisplayWindowMouseMoveEvent);
|
|
|
|
|
connect(widget, &QtDisplayWidget::windowMouseButtonEvent, this, &QtHostInterface::onDisplayWindowMouseButtonEvent);
|
2021-02-25 16:42:20 +00:00
|
|
|
|
connect(widget, &QtDisplayWidget::windowMouseWheelEvent, this, &QtHostInterface::onDisplayWindowMouseWheelEvent);
|
2020-04-22 11:13:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::updateDisplayState()
|
|
|
|
|
{
|
2020-07-22 00:14:11 +00:00
|
|
|
|
if (!m_display)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-04-22 11:13:51 +00:00
|
|
|
|
// this expects the context to get moved back to us afterwards
|
2020-06-29 16:46:57 +00:00
|
|
|
|
m_display->DoneRenderContextCurrent();
|
|
|
|
|
|
2021-02-25 02:51:43 +00:00
|
|
|
|
QtDisplayWidget* display_widget =
|
|
|
|
|
updateDisplayRequested(m_worker_thread, m_is_fullscreen, m_is_rendering_to_main && !m_is_fullscreen);
|
2020-06-29 16:46:57 +00:00
|
|
|
|
if (!display_widget || !m_display->MakeRenderContextCurrent())
|
2020-04-22 11:13:51 +00:00
|
|
|
|
Panic("Failed to make device context current after updating");
|
|
|
|
|
|
2020-06-29 16:46:57 +00:00
|
|
|
|
connectDisplaySignals(display_widget);
|
2020-11-01 14:38:54 +00:00
|
|
|
|
m_is_exclusive_fullscreen = m_display->IsFullscreen();
|
2020-08-02 17:06:03 +00:00
|
|
|
|
|
2021-02-21 16:38:16 +00:00
|
|
|
|
OnHostDisplayResized(m_display->GetWindowWidth(), m_display->GetWindowHeight(), m_display->GetWindowScale());
|
|
|
|
|
|
2020-08-02 17:06:03 +00:00
|
|
|
|
if (!System::IsShutdown())
|
|
|
|
|
{
|
2020-12-27 04:08:13 +00:00
|
|
|
|
UpdateSoftwareCursor();
|
2021-02-21 16:38:16 +00:00
|
|
|
|
|
|
|
|
|
if (!m_fullscreen_ui_enabled)
|
|
|
|
|
redrawDisplayWindow();
|
2020-08-02 17:06:03 +00:00
|
|
|
|
}
|
2021-02-21 16:38:16 +00:00
|
|
|
|
|
2020-04-22 11:13:51 +00:00
|
|
|
|
UpdateSpeedLimiterState();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
|
void QtHostInterface::ReleaseHostDisplay()
|
|
|
|
|
{
|
2020-04-22 11:13:51 +00:00
|
|
|
|
Assert(m_display);
|
|
|
|
|
|
2020-10-01 13:32:01 +00:00
|
|
|
|
ReleaseHostDisplayResources();
|
2020-06-29 16:46:57 +00:00
|
|
|
|
m_display->DestroyRenderDevice();
|
2020-04-22 11:13:51 +00:00
|
|
|
|
emit destroyDisplayRequested();
|
2020-06-29 16:46:57 +00:00
|
|
|
|
m_display.reset();
|
2020-04-25 04:26:57 +00:00
|
|
|
|
m_is_fullscreen = false;
|
2020-02-15 15:14:28 +00:00
|
|
|
|
}
|
2020-01-24 04:51:52 +00:00
|
|
|
|
|
2020-04-05 12:58:54 +00:00
|
|
|
|
bool QtHostInterface::IsFullscreen() const
|
2020-02-15 15:14:28 +00:00
|
|
|
|
{
|
2020-04-05 12:58:54 +00:00
|
|
|
|
return m_is_fullscreen;
|
2020-02-28 07:00:09 +00:00
|
|
|
|
}
|
2020-01-24 04:51:52 +00:00
|
|
|
|
|
2020-04-05 12:58:54 +00:00
|
|
|
|
bool QtHostInterface::SetFullscreen(bool enabled)
|
2020-02-28 07:00:09 +00:00
|
|
|
|
{
|
2020-04-05 12:58:54 +00:00
|
|
|
|
if (m_is_fullscreen == enabled)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
m_is_fullscreen = enabled;
|
2020-04-22 11:13:51 +00:00
|
|
|
|
updateDisplayState();
|
2020-04-05 12:58:54 +00:00
|
|
|
|
return true;
|
2020-02-28 07:00:09 +00:00
|
|
|
|
}
|
2020-02-15 15:14:35 +00:00
|
|
|
|
|
2020-11-18 13:15:16 +00:00
|
|
|
|
bool QtHostInterface::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
|
|
|
|
|
{
|
|
|
|
|
if (new_window_width <= 0 || new_window_height <= 0 || m_is_fullscreen || m_is_exclusive_fullscreen)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
emit displaySizeRequested(new_window_width, new_window_height);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-29 06:29:14 +00:00
|
|
|
|
void* QtHostInterface::GetTopLevelWindowHandle() const
|
|
|
|
|
{
|
|
|
|
|
return reinterpret_cast<void*>(m_main_window->winId());
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-13 12:13:46 +00:00
|
|
|
|
void QtHostInterface::RequestExit()
|
|
|
|
|
{
|
|
|
|
|
emit exitRequested();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-28 07:00:09 +00:00
|
|
|
|
std::optional<CommonHostInterface::HostKeyCode> QtHostInterface::GetHostKeyCode(const std::string_view key_code) const
|
|
|
|
|
{
|
|
|
|
|
const std::optional<int> code =
|
|
|
|
|
QtUtils::ParseKeyString(QString::fromUtf8(key_code.data(), static_cast<int>(key_code.length())));
|
|
|
|
|
if (!code)
|
|
|
|
|
return std::nullopt;
|
2020-02-25 13:40:46 +00:00
|
|
|
|
|
2020-02-28 07:00:09 +00:00
|
|
|
|
return static_cast<s32>(*code);
|
2020-01-24 04:51:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
|
void QtHostInterface::OnSystemCreated()
|
|
|
|
|
{
|
2020-03-12 03:53:58 +00:00
|
|
|
|
CommonHostInterface::OnSystemCreated();
|
2020-02-15 15:14:28 +00:00
|
|
|
|
|
|
|
|
|
wakeThread();
|
2020-04-29 03:27:58 +00:00
|
|
|
|
stopBackgroundControllerPollTimer();
|
2020-02-15 15:14:28 +00:00
|
|
|
|
|
|
|
|
|
emit emulationStarted();
|
2020-05-22 08:01:12 +00:00
|
|
|
|
emit emulationPaused(false);
|
2020-02-15 15:14:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::OnSystemPaused(bool paused)
|
|
|
|
|
{
|
2020-03-12 03:53:58 +00:00
|
|
|
|
CommonHostInterface::OnSystemPaused(paused);
|
2020-02-15 15:14:28 +00:00
|
|
|
|
|
2020-02-26 09:26:14 +00:00
|
|
|
|
emit emulationPaused(paused);
|
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
|
if (!paused)
|
2020-02-26 09:26:14 +00:00
|
|
|
|
{
|
2020-02-15 15:14:28 +00:00
|
|
|
|
wakeThread();
|
2020-04-29 03:27:58 +00:00
|
|
|
|
stopBackgroundControllerPollTimer();
|
2020-02-26 09:26:14 +00:00
|
|
|
|
emit focusDisplayWidgetRequested();
|
|
|
|
|
}
|
2020-04-29 03:27:58 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
startBackgroundControllerPollTimer();
|
|
|
|
|
}
|
2020-02-15 15:14:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::OnSystemDestroyed()
|
|
|
|
|
{
|
2020-05-01 06:42:43 +00:00
|
|
|
|
CommonHostInterface::OnSystemDestroyed();
|
2020-02-15 15:14:28 +00:00
|
|
|
|
|
2020-08-29 12:19:09 +00:00
|
|
|
|
ClearOSDMessages();
|
2020-04-29 03:27:58 +00:00
|
|
|
|
startBackgroundControllerPollTimer();
|
2020-02-15 15:14:28 +00:00
|
|
|
|
emit emulationStopped();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-09 13:16:25 +00:00
|
|
|
|
void QtHostInterface::OnSystemPerformanceCountersUpdated()
|
2020-01-24 04:49:47 +00:00
|
|
|
|
{
|
2020-02-09 13:16:25 +00:00
|
|
|
|
HostInterface::OnSystemPerformanceCountersUpdated();
|
2020-01-24 04:49:47 +00:00
|
|
|
|
|
2020-07-31 07:09:18 +00:00
|
|
|
|
emit systemPerformanceCountersUpdated(System::GetEmulationSpeed(), System::GetFPS(), System::GetVPS(),
|
|
|
|
|
System::GetAverageFrameTime(), System::GetWorstFrameTime());
|
2020-01-24 04:49:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-21 06:53:14 +00:00
|
|
|
|
void QtHostInterface::OnRunningGameChanged(const std::string& path, CDImage* image, const std::string& game_code,
|
|
|
|
|
const std::string& game_title)
|
2020-01-24 04:50:46 +00:00
|
|
|
|
{
|
2021-02-21 06:53:14 +00:00
|
|
|
|
CommonHostInterface::OnRunningGameChanged(path, image, game_code, game_title);
|
2020-01-24 04:50:46 +00:00
|
|
|
|
|
2020-07-31 07:09:18 +00:00
|
|
|
|
if (!System::IsShutdown())
|
2020-01-24 04:51:05 +00:00
|
|
|
|
{
|
2020-07-31 07:09:18 +00:00
|
|
|
|
emit runningGameChanged(QString::fromStdString(System::GetRunningPath()),
|
|
|
|
|
QString::fromStdString(System::GetRunningCode()),
|
|
|
|
|
QString::fromStdString(System::GetRunningTitle()));
|
2020-01-24 04:51:05 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
emit runningGameChanged(QString(), QString(), QString());
|
|
|
|
|
}
|
2020-01-24 04:50:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-28 07:00:09 +00:00
|
|
|
|
void QtHostInterface::OnSystemStateSaved(bool global, s32 slot)
|
|
|
|
|
{
|
2020-07-31 07:09:18 +00:00
|
|
|
|
emit stateSaved(QString::fromStdString(System::GetRunningCode()), global, slot);
|
2020-02-28 07:00:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-05 12:58:47 +00:00
|
|
|
|
void QtHostInterface::SetDefaultSettings(SettingsInterface& si)
|
|
|
|
|
{
|
|
|
|
|
CommonHostInterface::SetDefaultSettings(si);
|
|
|
|
|
|
2021-02-01 15:46:16 +00:00
|
|
|
|
si.SetStringValue("Hotkeys", "PowerOff", "Keyboard/Escape");
|
|
|
|
|
si.SetStringValue("Hotkeys", "LoadSelectedSaveState", "Keyboard/F1");
|
|
|
|
|
si.SetStringValue("Hotkeys", "SaveSelectedSaveState", "Keyboard/F2");
|
|
|
|
|
si.SetStringValue("Hotkeys", "SelectPreviousSaveStateSlot", "Keyboard/F3");
|
|
|
|
|
si.SetStringValue("Hotkeys", "SelectNextSaveStateSlot", "Keyboard/F4");
|
|
|
|
|
|
2020-04-05 12:58:47 +00:00
|
|
|
|
si.SetBoolValue("Main", "RenderToMainWindow", true);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-27 04:08:13 +00:00
|
|
|
|
void QtHostInterface::SetMouseMode(bool relative, bool hide_cursor)
|
|
|
|
|
{
|
|
|
|
|
emit mouseModeRequested(relative, hide_cursor);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-02 06:13:03 +00:00
|
|
|
|
void QtHostInterface::updateInputMap()
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2020-02-28 07:00:09 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "updateInputMap", Qt::QueuedConnection);
|
2020-01-02 06:13:03 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-13 16:24:11 +00:00
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(m_settings_mutex);
|
|
|
|
|
CommonHostInterface::UpdateInputMap(*m_settings_interface.get());
|
2020-02-17 15:06:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-14 15:44:16 +00:00
|
|
|
|
void QtHostInterface::applyInputProfile(const QString& profile_path)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "applyInputProfile", Qt::QueuedConnection, Q_ARG(const QString&, profile_path));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-21 10:22:44 +00:00
|
|
|
|
ApplyInputProfile(profile_path.toUtf8().data());
|
2020-04-14 15:44:16 +00:00
|
|
|
|
emit inputProfileLoaded();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::saveInputProfile(const QString& profile_name)
|
|
|
|
|
{
|
2020-07-13 16:24:11 +00:00
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(m_settings_mutex);
|
2021-02-21 10:22:44 +00:00
|
|
|
|
SaveInputProfile(profile_name.toUtf8().data());
|
2020-04-14 15:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-20 13:26:24 +00:00
|
|
|
|
QString QtHostInterface::getUserDirectoryRelativePath(const QString& arg) const
|
|
|
|
|
{
|
|
|
|
|
QString result = QString::fromStdString(m_user_directory);
|
2020-12-25 09:36:48 +00:00
|
|
|
|
result += FS_OSPATH_SEPARATOR_CHARACTER;
|
2020-05-20 13:26:24 +00:00
|
|
|
|
result += arg;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QtHostInterface::getProgramDirectoryRelativePath(const QString& arg) const
|
|
|
|
|
{
|
|
|
|
|
QString result = QString::fromStdString(m_program_directory);
|
2020-12-25 09:36:48 +00:00
|
|
|
|
result += FS_OSPATH_SEPARATOR_CHARACTER;
|
2020-05-20 13:26:24 +00:00
|
|
|
|
result += arg;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-06 12:08:22 +00:00
|
|
|
|
QString QtHostInterface::getProgramDirectory() const
|
|
|
|
|
{
|
|
|
|
|
return QString::fromStdString(m_program_directory);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 15:14:49 +00:00
|
|
|
|
void QtHostInterface::powerOffSystem()
|
2019-12-31 06:17:17 +00:00
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2021-02-18 15:48:44 +00:00
|
|
|
|
System::CancelPendingStartup();
|
2020-02-15 15:14:49 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "powerOffSystem", Qt::QueuedConnection);
|
2019-12-31 06:17:17 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-02 15:14:05 +00:00
|
|
|
|
PowerOffSystem(ShouldSaveResumeState());
|
2021-01-08 13:42:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::powerOffSystemWithoutSaving()
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2021-02-18 15:48:44 +00:00
|
|
|
|
System::CancelPendingStartup();
|
2021-01-08 13:42:17 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "powerOffSystemWithoutSaving", Qt::QueuedConnection);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-02 15:14:05 +00:00
|
|
|
|
PowerOffSystem(false);
|
2019-12-31 06:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 15:14:49 +00:00
|
|
|
|
void QtHostInterface::synchronousPowerOffSystem()
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
2021-02-18 15:48:44 +00:00
|
|
|
|
{
|
|
|
|
|
System::CancelPendingStartup();
|
2020-02-15 15:14:49 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "powerOffSystem", Qt::BlockingQueuedConnection);
|
2021-02-18 15:48:44 +00:00
|
|
|
|
}
|
2020-02-15 15:14:49 +00:00
|
|
|
|
else
|
2021-02-18 15:48:44 +00:00
|
|
|
|
{
|
2020-02-15 15:14:49 +00:00
|
|
|
|
powerOffSystem();
|
2021-02-18 15:48:44 +00:00
|
|
|
|
}
|
2020-02-15 15:14:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-31 06:17:17 +00:00
|
|
|
|
void QtHostInterface::resetSystem()
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "resetSystem", Qt::QueuedConnection);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 07:09:18 +00:00
|
|
|
|
if (System::IsShutdown())
|
2019-12-31 06:17:17 +00:00
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("resetSystem() called without system");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HostInterface::ResetSystem();
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-16 15:18:13 +00:00
|
|
|
|
void QtHostInterface::pauseSystem(bool paused, bool wait_until_paused /* = false */)
|
2019-12-31 06:17:17 +00:00
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2020-12-16 15:18:13 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "pauseSystem",
|
|
|
|
|
wait_until_paused ? Qt::BlockingQueuedConnection : Qt::QueuedConnection,
|
|
|
|
|
Q_ARG(bool, paused), Q_ARG(bool, wait_until_paused));
|
2019-12-31 06:17:17 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-12 03:53:58 +00:00
|
|
|
|
CommonHostInterface::PauseSystem(paused);
|
2019-12-31 06:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 15:14:33 +00:00
|
|
|
|
void QtHostInterface::changeDisc(const QString& new_disc_filename)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "changeDisc", Qt::QueuedConnection, Q_ARG(const QString&, new_disc_filename));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 07:09:18 +00:00
|
|
|
|
if (System::IsShutdown())
|
2020-02-15 15:14:33 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2020-05-19 16:32:19 +00:00
|
|
|
|
if (!new_disc_filename.isEmpty())
|
2020-07-31 07:09:18 +00:00
|
|
|
|
System::InsertMedia(new_disc_filename.toStdString().c_str());
|
2020-05-19 16:32:19 +00:00
|
|
|
|
else
|
2020-07-31 07:09:18 +00:00
|
|
|
|
System::RemoveMedia();
|
2020-02-15 15:14:33 +00:00
|
|
|
|
}
|
2019-12-31 06:17:17 +00:00
|
|
|
|
|
2020-08-15 10:39:11 +00:00
|
|
|
|
void QtHostInterface::changeDiscFromPlaylist(quint32 index)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "changeDiscFromPlaylist", Qt::QueuedConnection, Q_ARG(quint32, index));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (System::IsShutdown())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!System::SwitchMediaFromPlaylist(index))
|
|
|
|
|
ReportFormattedError("Failed to switch to playlist index %u", index);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-12 03:51:23 +00:00
|
|
|
|
static QString FormatTimestampForSaveStateMenu(u64 timestamp)
|
|
|
|
|
{
|
|
|
|
|
const QDateTime qtime(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(timestamp)));
|
2020-07-30 17:40:51 +00:00
|
|
|
|
return qtime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
|
2020-03-12 03:51:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 15:14:04 +00:00
|
|
|
|
void QtHostInterface::populateSaveStateMenus(const char* game_code, QMenu* load_menu, QMenu* save_menu)
|
|
|
|
|
{
|
2020-08-15 11:20:37 +00:00
|
|
|
|
auto add_slot = [this, game_code, load_menu, save_menu](const QString& title, const QString& empty_title, bool global,
|
2020-03-12 03:51:23 +00:00
|
|
|
|
s32 slot) {
|
|
|
|
|
std::optional<SaveStateInfo> ssi = GetSaveStateInfo(global ? nullptr : game_code, slot);
|
2020-02-15 15:14:04 +00:00
|
|
|
|
|
2020-08-15 11:20:37 +00:00
|
|
|
|
const QString menu_title =
|
|
|
|
|
ssi.has_value() ? title.arg(slot).arg(FormatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot);
|
2020-03-12 03:51:23 +00:00
|
|
|
|
|
|
|
|
|
QAction* load_action = load_menu->addAction(menu_title);
|
|
|
|
|
load_action->setEnabled(ssi.has_value());
|
|
|
|
|
if (ssi.has_value())
|
2020-02-15 15:14:04 +00:00
|
|
|
|
{
|
2020-03-12 03:51:23 +00:00
|
|
|
|
const QString path(QString::fromStdString(ssi->path));
|
|
|
|
|
connect(load_action, &QAction::triggered, [this, path]() { loadState(path); });
|
2020-02-15 15:14:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-12 03:51:23 +00:00
|
|
|
|
QAction* save_action = save_menu->addAction(menu_title);
|
|
|
|
|
connect(save_action, &QAction::triggered, [this, global, slot]() { saveState(global, slot); });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
load_menu->clear();
|
2020-02-15 15:14:04 +00:00
|
|
|
|
save_menu->clear();
|
2020-03-12 03:51:23 +00:00
|
|
|
|
|
2020-02-15 15:14:04 +00:00
|
|
|
|
if (game_code && std::strlen(game_code) > 0)
|
|
|
|
|
{
|
2020-03-12 03:51:23 +00:00
|
|
|
|
for (u32 slot = 1; slot <= PER_GAME_SAVE_STATE_SLOTS; slot++)
|
2020-08-15 11:20:37 +00:00
|
|
|
|
add_slot(tr("Game Save %1 (%2)"), tr("Game Save %1 (Empty)"), false, static_cast<s32>(slot));
|
2020-02-15 15:14:04 +00:00
|
|
|
|
|
2020-03-12 03:51:23 +00:00
|
|
|
|
load_menu->addSeparator();
|
2020-02-15 15:14:04 +00:00
|
|
|
|
save_menu->addSeparator();
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-12 03:51:23 +00:00
|
|
|
|
for (u32 slot = 1; slot <= GLOBAL_SAVE_STATE_SLOTS; slot++)
|
2020-08-15 11:20:37 +00:00
|
|
|
|
add_slot(tr("Global Save %1 (%2)"), tr("Global Save %1 (Empty)"), true, static_cast<s32>(slot));
|
2020-02-15 15:14:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-10 13:24:07 +00:00
|
|
|
|
void QtHostInterface::populateGameListContextMenu(const GameListEntry* entry, QWidget* parent_window, QMenu* menu)
|
2020-03-02 01:08:16 +00:00
|
|
|
|
{
|
|
|
|
|
QAction* resume_action = menu->addAction(tr("Resume"));
|
|
|
|
|
resume_action->setEnabled(false);
|
|
|
|
|
|
|
|
|
|
QMenu* load_state_menu = menu->addMenu(tr("Load State"));
|
|
|
|
|
load_state_menu->setEnabled(false);
|
|
|
|
|
|
2020-12-01 14:56:31 +00:00
|
|
|
|
if (!entry->code.empty())
|
2020-03-02 01:08:16 +00:00
|
|
|
|
{
|
2020-12-01 14:56:31 +00:00
|
|
|
|
const std::vector<SaveStateInfo> available_states(GetAvailableSaveStates(entry->code.c_str()));
|
|
|
|
|
const QString timestamp_format = QLocale::system().dateTimeFormat(QLocale::ShortFormat);
|
2021-03-03 09:14:01 +00:00
|
|
|
|
const bool challenge_mode = IsCheevosChallengeModeActive();
|
2020-12-01 14:56:31 +00:00
|
|
|
|
for (const SaveStateInfo& ssi : available_states)
|
|
|
|
|
{
|
|
|
|
|
if (ssi.global)
|
|
|
|
|
continue;
|
2020-03-02 01:08:16 +00:00
|
|
|
|
|
2020-12-01 14:56:31 +00:00
|
|
|
|
const s32 slot = ssi.slot;
|
|
|
|
|
const QDateTime timestamp(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(ssi.timestamp)));
|
|
|
|
|
const QString timestamp_str(timestamp.toString(timestamp_format));
|
|
|
|
|
const QString path(QString::fromStdString(ssi.path));
|
2020-03-02 01:08:16 +00:00
|
|
|
|
|
2020-12-01 14:56:31 +00:00
|
|
|
|
QAction* action;
|
|
|
|
|
if (slot < 0)
|
|
|
|
|
{
|
|
|
|
|
resume_action->setText(tr("Resume (%1)").arg(timestamp_str));
|
2021-03-03 09:14:01 +00:00
|
|
|
|
resume_action->setEnabled(!challenge_mode);
|
2020-12-01 14:56:31 +00:00
|
|
|
|
action = resume_action;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
load_state_menu->setEnabled(true);
|
|
|
|
|
action = load_state_menu->addAction(tr("Game Save %1 (%2)").arg(slot).arg(timestamp_str));
|
|
|
|
|
}
|
2020-03-02 01:08:16 +00:00
|
|
|
|
|
2021-03-03 09:14:01 +00:00
|
|
|
|
action->setDisabled(challenge_mode);
|
2020-12-01 14:56:31 +00:00
|
|
|
|
connect(action, &QAction::triggered, [this, path]() { loadState(path); });
|
|
|
|
|
}
|
2020-03-02 01:08:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-10 13:24:07 +00:00
|
|
|
|
QAction* open_memory_cards_action = menu->addAction(tr("Edit Memory Cards..."));
|
|
|
|
|
connect(open_memory_cards_action, &QAction::triggered, [this, entry]() {
|
2020-12-22 15:39:59 +00:00
|
|
|
|
QString paths[2];
|
2020-11-10 13:24:07 +00:00
|
|
|
|
for (u32 i = 0; i < 2; i++)
|
|
|
|
|
{
|
|
|
|
|
MemoryCardType type = g_settings.memory_card_types[i];
|
|
|
|
|
if (entry->code.empty() && type == MemoryCardType::PerGame)
|
|
|
|
|
type = MemoryCardType::Shared;
|
|
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case MemoryCardType::None:
|
|
|
|
|
continue;
|
|
|
|
|
case MemoryCardType::Shared:
|
2020-12-22 15:39:59 +00:00
|
|
|
|
if (g_settings.memory_card_paths[i].empty())
|
|
|
|
|
{
|
|
|
|
|
paths[i] = QString::fromStdString(GetSharedMemoryCardPath(i));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
QFileInfo path(QString::fromStdString(g_settings.memory_card_paths[i]));
|
|
|
|
|
path.makeAbsolute();
|
|
|
|
|
paths[i] = QDir::toNativeSeparators(path.canonicalFilePath());
|
|
|
|
|
}
|
2020-11-10 13:24:07 +00:00
|
|
|
|
break;
|
|
|
|
|
case MemoryCardType::PerGame:
|
2020-12-22 15:39:59 +00:00
|
|
|
|
paths[i] = QString::fromStdString(GetGameMemoryCardPath(entry->code.c_str(), i));
|
2020-11-10 13:24:07 +00:00
|
|
|
|
break;
|
|
|
|
|
case MemoryCardType::PerGameTitle:
|
2020-12-22 15:39:59 +00:00
|
|
|
|
paths[i] = QString::fromStdString(GetGameMemoryCardPath(entry->title.c_str(), i));
|
2020-11-10 13:24:07 +00:00
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-22 15:39:59 +00:00
|
|
|
|
m_main_window->openMemoryCardEditor(paths[0], paths[1]);
|
2020-11-10 13:24:07 +00:00
|
|
|
|
});
|
|
|
|
|
|
2020-03-02 01:08:16 +00:00
|
|
|
|
const bool has_any_states = resume_action->isEnabled() || load_state_menu->isEnabled();
|
|
|
|
|
QAction* delete_save_states_action = menu->addAction(tr("Delete Save States..."));
|
|
|
|
|
delete_save_states_action->setEnabled(has_any_states);
|
|
|
|
|
if (has_any_states)
|
|
|
|
|
{
|
2020-11-10 13:24:07 +00:00
|
|
|
|
connect(delete_save_states_action, &QAction::triggered, [this, parent_window, entry] {
|
2020-03-02 01:08:16 +00:00
|
|
|
|
if (QMessageBox::warning(
|
|
|
|
|
parent_window, tr("Confirm Save State Deletion"),
|
|
|
|
|
tr("Are you sure you want to delete all save states for %1?\n\nThe saves will not be recoverable.")
|
2020-11-10 13:24:07 +00:00
|
|
|
|
.arg(QString::fromStdString(entry->code)),
|
2020-03-02 01:08:16 +00:00
|
|
|
|
QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-10 13:24:07 +00:00
|
|
|
|
DeleteSaveStates(entry->code.c_str(), true);
|
2020-03-02 01:08:16 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-15 10:39:11 +00:00
|
|
|
|
void QtHostInterface::populatePlaylistEntryMenu(QMenu* menu)
|
|
|
|
|
{
|
|
|
|
|
if (!System::IsValid())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
QActionGroup* ag = new QActionGroup(menu);
|
|
|
|
|
const u32 count = System::GetMediaPlaylistCount();
|
|
|
|
|
const u32 current = System::GetMediaPlaylistIndex();
|
|
|
|
|
for (u32 i = 0; i < count; i++)
|
|
|
|
|
{
|
|
|
|
|
QAction* action = ag->addAction(QString::fromStdString(System::GetMediaPlaylistPath(i)));
|
|
|
|
|
action->setCheckable(true);
|
|
|
|
|
action->setChecked(i == current);
|
|
|
|
|
connect(action, &QAction::triggered, [this, i]() { changeDiscFromPlaylist(i); });
|
|
|
|
|
menu->addAction(action);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-09 13:44:21 +00:00
|
|
|
|
void QtHostInterface::populateCheatsMenu(QMenu* menu)
|
|
|
|
|
{
|
|
|
|
|
Assert(!isOnWorkerThread());
|
|
|
|
|
if (!System::IsValid())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const bool has_cheat_list = System::HasCheatList();
|
|
|
|
|
|
|
|
|
|
QMenu* enabled_menu = menu->addMenu(tr("&Enabled Cheats"));
|
|
|
|
|
enabled_menu->setEnabled(has_cheat_list);
|
|
|
|
|
QMenu* apply_menu = menu->addMenu(tr("&Apply Cheats"));
|
|
|
|
|
apply_menu->setEnabled(has_cheat_list);
|
|
|
|
|
if (has_cheat_list)
|
|
|
|
|
{
|
|
|
|
|
CheatList* cl = System::GetCheatList();
|
|
|
|
|
for (u32 i = 0; i < cl->GetCodeCount(); i++)
|
|
|
|
|
{
|
|
|
|
|
CheatCode& cc = cl->GetCode(i);
|
|
|
|
|
QString desc(QString::fromStdString(cc.description));
|
2020-10-21 11:31:59 +00:00
|
|
|
|
if (cc.IsManuallyActivated())
|
|
|
|
|
{
|
|
|
|
|
QAction* action = apply_menu->addAction(desc);
|
|
|
|
|
connect(action, &QAction::triggered, [this, i]() { applyCheat(i); });
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
QAction* action = enabled_menu->addAction(desc);
|
|
|
|
|
action->setCheckable(true);
|
|
|
|
|
action->setChecked(cc.enabled);
|
|
|
|
|
connect(action, &QAction::toggled, [this, i](bool enabled) { setCheatEnabled(i, enabled); });
|
|
|
|
|
}
|
2020-09-09 13:44:21 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::loadCheatList(const QString& filename)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "loadCheatList", Qt::QueuedConnection, Q_ARG(const QString&, filename));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LoadCheatList(filename.toUtf8().constData());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::setCheatEnabled(quint32 index, bool enabled)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "setCheatEnabled", Qt::QueuedConnection, Q_ARG(quint32, index),
|
|
|
|
|
Q_ARG(bool, enabled));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetCheatCodeState(index, enabled, g_settings.auto_load_cheats);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-13 10:21:33 +00:00
|
|
|
|
void QtHostInterface::applyCheat(quint32 index)
|
2020-09-09 13:44:21 +00:00
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "applyCheat", Qt::QueuedConnection, Q_ARG(quint32, index));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ApplyCheatCode(index);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-13 10:21:33 +00:00
|
|
|
|
void QtHostInterface::reloadPostProcessingShaders()
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "reloadPostProcessingShaders", Qt::QueuedConnection);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ReloadPostProcessingShaders();
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-18 13:15:16 +00:00
|
|
|
|
void QtHostInterface::requestRenderWindowScale(qreal scale)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "requestRenderWindowScale", Qt::QueuedConnection, Q_ARG(qreal, scale));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RequestRenderWindowScale(scale);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 15:14:49 +00:00
|
|
|
|
void QtHostInterface::executeOnEmulationThread(std::function<void()> callback, bool wait)
|
|
|
|
|
{
|
|
|
|
|
if (isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
callback();
|
|
|
|
|
if (wait)
|
|
|
|
|
m_worker_thread_sync_execute_done.Signal();
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QMetaObject::invokeMethod(this, "executeOnEmulationThread", Qt::QueuedConnection,
|
2021-01-30 05:52:36 +00:00
|
|
|
|
Q_ARG(std::function<void()>, std::move(callback)), Q_ARG(bool, wait));
|
2020-10-19 15:14:49 +00:00
|
|
|
|
if (wait)
|
|
|
|
|
{
|
|
|
|
|
// don't deadlock
|
|
|
|
|
while (!m_worker_thread_sync_execute_done.TryWait(10))
|
|
|
|
|
qApp->processEvents(QEventLoop::ExcludeSocketNotifiers);
|
|
|
|
|
m_worker_thread_sync_execute_done.Reset();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-30 05:52:36 +00:00
|
|
|
|
void QtHostInterface::RunLater(std::function<void()> func)
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "executeOnEmulationThread", Qt::QueuedConnection,
|
|
|
|
|
Q_ARG(std::function<void()>, std::move(func)), Q_ARG(bool, false));
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 15:14:30 +00:00
|
|
|
|
void QtHostInterface::loadState(const QString& filename)
|
2020-02-15 15:14:04 +00:00
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2020-02-15 15:14:30 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "loadState", Qt::QueuedConnection, Q_ARG(const QString&, filename));
|
2020-02-15 15:14:04 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 15:39:57 +00:00
|
|
|
|
if (System::IsShutdown())
|
|
|
|
|
emit emulationStarting();
|
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
|
LoadState(filename.toStdString().c_str());
|
2020-08-22 08:16:46 +00:00
|
|
|
|
if (System::IsValid())
|
|
|
|
|
renderDisplay();
|
2020-02-15 15:14:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::loadState(bool global, qint32 slot)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "loadState", Qt::QueuedConnection, Q_ARG(bool, global), Q_ARG(qint32, slot));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
|
LoadState(global, slot);
|
2020-08-22 08:16:46 +00:00
|
|
|
|
if (System::IsValid())
|
|
|
|
|
renderDisplay();
|
2020-02-15 15:14:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::saveState(bool global, qint32 slot, bool block_until_done /* = false */)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "saveState", block_until_done ? Qt::BlockingQueuedConnection : Qt::QueuedConnection,
|
|
|
|
|
Q_ARG(bool, global), Q_ARG(qint32, slot), Q_ARG(bool, block_until_done));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 07:09:18 +00:00
|
|
|
|
if (!System::IsShutdown())
|
2020-02-15 15:14:04 +00:00
|
|
|
|
SaveState(global, slot);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-08 14:48:00 +00:00
|
|
|
|
void QtHostInterface::setAudioOutputVolume(int volume, int fast_forward_volume)
|
2020-07-21 10:04:43 +00:00
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2020-12-15 14:12:20 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "setAudioOutputVolume", Qt::QueuedConnection, Q_ARG(int, volume),
|
|
|
|
|
Q_ARG(int, fast_forward_volume));
|
2020-07-21 10:04:43 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-08 14:48:00 +00:00
|
|
|
|
g_settings.audio_output_volume = volume;
|
|
|
|
|
g_settings.audio_fast_forward_volume = fast_forward_volume;
|
2021-01-12 16:36:11 +00:00
|
|
|
|
|
|
|
|
|
if (m_audio_stream)
|
|
|
|
|
m_audio_stream->SetOutputVolume(GetAudioOutputVolume());
|
2020-07-23 16:55:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::setAudioOutputMuted(bool muted)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2020-12-15 14:12:20 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "setAudioOutputMuted", Qt::QueuedConnection, Q_ARG(bool, muted));
|
2020-07-23 16:55:00 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-12 01:49:50 +00:00
|
|
|
|
g_settings.audio_output_muted = muted;
|
|
|
|
|
|
2020-07-23 16:55:00 +00:00
|
|
|
|
if (m_audio_stream)
|
2020-12-08 14:48:00 +00:00
|
|
|
|
m_audio_stream->SetOutputVolume(GetAudioOutputVolume());
|
2020-07-21 10:04:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-15 12:04:32 +00:00
|
|
|
|
void QtHostInterface::startDumpingAudio()
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2020-12-15 14:12:20 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "startDumpingAudio", Qt::QueuedConnection);
|
2020-03-15 12:04:32 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StartDumpingAudio();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::stopDumpingAudio()
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2020-12-15 14:12:20 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "stopDumpingAudio", Qt::QueuedConnection);
|
2020-03-15 12:04:32 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StopDumpingAudio();
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-16 15:18:13 +00:00
|
|
|
|
void QtHostInterface::singleStepCPU()
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "singleStepCPU", Qt::BlockingQueuedConnection);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!System::IsValid())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
System::SingleStepCPU();
|
|
|
|
|
renderDisplay();
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-16 13:20:36 +00:00
|
|
|
|
void QtHostInterface::dumpRAM(const QString& filename)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2020-12-15 14:12:20 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "dumpRAM", Qt::QueuedConnection, Q_ARG(const QString&, filename));
|
2020-08-16 13:20:36 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string filename_str = filename.toStdString();
|
|
|
|
|
if (System::DumpRAM(filename_str.c_str()))
|
|
|
|
|
ReportFormattedMessage("RAM dumped to '%s'", filename_str.c_str());
|
|
|
|
|
else
|
|
|
|
|
ReportFormattedMessage("Failed to dump RAM to '%s'", filename_str.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-13 09:24:41 +00:00
|
|
|
|
void QtHostInterface::dumpVRAM(const QString& filename)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "dumpVRAM", Qt::QueuedConnection, Q_ARG(const QString&, filename));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string filename_str = filename.toStdString();
|
|
|
|
|
if (System::DumpVRAM(filename_str.c_str()))
|
|
|
|
|
ReportFormattedMessage("VRAM dumped to '%s'", filename_str.c_str());
|
|
|
|
|
else
|
|
|
|
|
ReportFormattedMessage("Failed to dump VRAM to '%s'", filename_str.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::dumpSPURAM(const QString& filename)
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, "dumpSPURAM", Qt::QueuedConnection, Q_ARG(const QString&, filename));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string filename_str = filename.toStdString();
|
|
|
|
|
if (System::DumpSPURAM(filename_str.c_str()))
|
|
|
|
|
ReportFormattedMessage("SPU RAM dumped to '%s'", filename_str.c_str());
|
|
|
|
|
else
|
|
|
|
|
ReportFormattedMessage("Failed to dump SPU RAM to '%s'", filename_str.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-15 14:06:39 +00:00
|
|
|
|
void QtHostInterface::saveScreenshot()
|
|
|
|
|
{
|
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
|
{
|
2020-12-16 15:25:57 +00:00
|
|
|
|
QMetaObject::invokeMethod(this, "saveScreenshot", Qt::QueuedConnection);
|
2020-03-15 14:06:39 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SaveScreenshot(nullptr, true, true);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-26 14:44:53 +00:00
|
|
|
|
void QtHostInterface::OnAchievementsRefreshed()
|
|
|
|
|
{
|
|
|
|
|
#ifdef WITH_CHEEVOS
|
|
|
|
|
QString game_info;
|
|
|
|
|
|
|
|
|
|
if (Cheevos::HasActiveGame())
|
|
|
|
|
{
|
|
|
|
|
game_info = tr("Game ID: %1\n"
|
|
|
|
|
"Game Title: %2\n"
|
|
|
|
|
"Game Developer: %3\n"
|
|
|
|
|
"Game Publisher: %4\n"
|
|
|
|
|
"Achievements: %5 (%6 points)\n\n")
|
|
|
|
|
.arg(Cheevos::GetGameID())
|
|
|
|
|
.arg(QString::fromStdString(Cheevos::GetGameTitle()))
|
|
|
|
|
.arg(QString::fromStdString(Cheevos::GetGameDeveloper()))
|
|
|
|
|
.arg(QString::fromStdString(Cheevos::GetGamePublisher()))
|
|
|
|
|
.arg(Cheevos::GetAchievementCount())
|
|
|
|
|
.arg(Cheevos::GetMaximumPointsForGame());
|
|
|
|
|
|
|
|
|
|
const std::string& rich_presence_string = Cheevos::GetRichPresenceString();
|
|
|
|
|
if (!rich_presence_string.empty())
|
|
|
|
|
game_info.append(QString::fromStdString(rich_presence_string));
|
|
|
|
|
else
|
|
|
|
|
game_info.append(tr("Rich presence inactive or unsupported."));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
game_info = tr("Game not loaded or no RetroAchievements available.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit achievementsLoaded(Cheevos::GetGameID(), game_info, Cheevos::GetAchievementCount(),
|
|
|
|
|
Cheevos::GetMaximumPointsForGame());
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2020-02-15 15:14:53 +00:00
|
|
|
|
void QtHostInterface::doBackgroundControllerPoll()
|
|
|
|
|
{
|
2020-04-30 15:01:50 +00:00
|
|
|
|
PollAndUpdate();
|
2020-02-15 15:14:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::createBackgroundControllerPollTimer()
|
|
|
|
|
{
|
|
|
|
|
DebugAssert(!m_background_controller_polling_timer);
|
|
|
|
|
m_background_controller_polling_timer = new QTimer(this);
|
|
|
|
|
m_background_controller_polling_timer->setSingleShot(false);
|
2020-04-30 02:34:09 +00:00
|
|
|
|
m_background_controller_polling_timer->setTimerType(Qt::CoarseTimer);
|
2020-02-15 15:14:53 +00:00
|
|
|
|
connect(m_background_controller_polling_timer, &QTimer::timeout, this, &QtHostInterface::doBackgroundControllerPoll);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::destroyBackgroundControllerPollTimer()
|
|
|
|
|
{
|
|
|
|
|
delete m_background_controller_polling_timer;
|
|
|
|
|
m_background_controller_polling_timer = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-29 03:27:58 +00:00
|
|
|
|
void QtHostInterface::startBackgroundControllerPollTimer()
|
|
|
|
|
{
|
|
|
|
|
if (m_background_controller_polling_timer->isActive() || !m_controller_interface)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_background_controller_polling_timer->start(BACKGROUND_CONTROLLER_POLLING_INTERVAL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::stopBackgroundControllerPollTimer()
|
|
|
|
|
{
|
|
|
|
|
if (!m_background_controller_polling_timer->isActive() || !m_controller_interface)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_background_controller_polling_timer->stop();
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-31 06:17:17 +00:00
|
|
|
|
void QtHostInterface::createThread()
|
|
|
|
|
{
|
|
|
|
|
m_original_thread = QThread::currentThread();
|
|
|
|
|
m_worker_thread = new Thread(this);
|
|
|
|
|
m_worker_thread->start();
|
|
|
|
|
moveToThread(m_worker_thread);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::stopThread()
|
|
|
|
|
{
|
|
|
|
|
Assert(!isOnWorkerThread());
|
|
|
|
|
|
|
|
|
|
QMetaObject::invokeMethod(this, "doStopThread", Qt::QueuedConnection);
|
|
|
|
|
m_worker_thread->wait();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::doStopThread()
|
|
|
|
|
{
|
|
|
|
|
m_shutdown_flag.store(true);
|
2020-01-11 04:21:08 +00:00
|
|
|
|
m_worker_thread_event_loop->quit();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::threadEntryPoint()
|
|
|
|
|
{
|
2020-01-10 03:28:13 +00:00
|
|
|
|
m_worker_thread_event_loop = new QEventLoop();
|
2020-01-11 03:49:52 +00:00
|
|
|
|
|
2020-02-15 15:14:53 +00:00
|
|
|
|
// set up controller interface and immediate poll to pick up the controller attached events
|
2020-04-05 12:59:06 +00:00
|
|
|
|
m_worker_thread->setInitResult(initializeOnThread());
|
2020-02-15 15:14:53 +00:00
|
|
|
|
|
2020-01-11 03:49:52 +00:00
|
|
|
|
// TODO: Event which flags the thread as ready
|
2019-12-31 06:17:17 +00:00
|
|
|
|
while (!m_shutdown_flag.load())
|
|
|
|
|
{
|
2021-02-21 16:38:16 +00:00
|
|
|
|
if (System::IsRunning())
|
2019-12-31 06:17:17 +00:00
|
|
|
|
{
|
2021-02-21 16:38:16 +00:00
|
|
|
|
if (m_display_all_frames)
|
|
|
|
|
System::RunFrame();
|
|
|
|
|
else
|
|
|
|
|
System::RunFrames();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
|
2021-02-21 16:38:16 +00:00
|
|
|
|
UpdateControllerRumble();
|
|
|
|
|
if (m_frame_step_request)
|
|
|
|
|
{
|
|
|
|
|
m_frame_step_request = false;
|
|
|
|
|
PauseSystem(true);
|
|
|
|
|
}
|
2021-01-28 10:20:15 +00:00
|
|
|
|
|
2021-02-21 16:38:16 +00:00
|
|
|
|
renderDisplay();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
|
2021-02-21 16:38:16 +00:00
|
|
|
|
System::UpdatePerformanceCounters();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
|
2021-02-21 16:38:16 +00:00
|
|
|
|
if (m_throttler_enabled)
|
|
|
|
|
System::Throttle();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// we want to keep rendering the UI when paused and fullscreen UI is enabled
|
|
|
|
|
if (!m_fullscreen_ui_enabled || !System::IsValid())
|
|
|
|
|
{
|
|
|
|
|
// wait until we have a system before running
|
|
|
|
|
m_worker_thread_event_loop->exec();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-03-23 14:20:56 +00:00
|
|
|
|
|
2021-02-21 16:38:16 +00:00
|
|
|
|
renderDisplay();
|
|
|
|
|
}
|
2019-12-31 06:17:17 +00:00
|
|
|
|
|
2020-01-10 03:28:13 +00:00
|
|
|
|
m_worker_thread_event_loop->processEvents(QEventLoop::AllEvents);
|
2020-04-30 15:01:50 +00:00
|
|
|
|
PollAndUpdate();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-05 12:59:06 +00:00
|
|
|
|
shutdownOnThread();
|
2020-04-05 12:58:47 +00:00
|
|
|
|
|
2020-01-10 03:28:13 +00:00
|
|
|
|
delete m_worker_thread_event_loop;
|
|
|
|
|
m_worker_thread_event_loop = nullptr;
|
2020-08-11 16:27:06 +00:00
|
|
|
|
if (m_settings_save_timer)
|
|
|
|
|
{
|
|
|
|
|
m_settings_save_timer.reset();
|
|
|
|
|
doSaveSettings();
|
|
|
|
|
}
|
2019-12-31 06:17:17 +00:00
|
|
|
|
|
|
|
|
|
// move back to UI thread
|
|
|
|
|
moveToThread(m_original_thread);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-18 12:27:45 +00:00
|
|
|
|
void QtHostInterface::renderDisplay()
|
|
|
|
|
{
|
2021-02-07 10:55:11 +00:00
|
|
|
|
ImGui::NewFrame();
|
2020-03-21 13:05:04 +00:00
|
|
|
|
DrawImGuiWindows();
|
2020-03-18 12:27:45 +00:00
|
|
|
|
m_display->Render();
|
2021-02-07 10:55:11 +00:00
|
|
|
|
ImGui::EndFrame();
|
2020-03-18 12:27:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-10 03:28:13 +00:00
|
|
|
|
void QtHostInterface::wakeThread()
|
|
|
|
|
{
|
|
|
|
|
if (isOnWorkerThread())
|
|
|
|
|
m_worker_thread_event_loop->quit();
|
|
|
|
|
else
|
|
|
|
|
QMetaObject::invokeMethod(m_worker_thread_event_loop, "quit", Qt::QueuedConnection);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 03:01:52 +00:00
|
|
|
|
static std::string GetFontPath(const char* name)
|
|
|
|
|
{
|
|
|
|
|
#ifdef WIN32
|
|
|
|
|
PWSTR folder_path;
|
|
|
|
|
if (FAILED(SHGetKnownFolderPath(FOLDERID_Fonts, 0, nullptr, &folder_path)))
|
|
|
|
|
return StringUtil::StdStringFromFormat("C:\\Windows\\Fonts\\%s", name);
|
|
|
|
|
|
|
|
|
|
std::string font_path(StringUtil::WideStringToUTF8String(folder_path));
|
|
|
|
|
CoTaskMemFree(folder_path);
|
|
|
|
|
font_path += "\\";
|
|
|
|
|
font_path += name;
|
|
|
|
|
return font_path;
|
|
|
|
|
#else
|
|
|
|
|
return name;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-21 16:38:16 +00:00
|
|
|
|
void QtHostInterface::setImGuiFont()
|
2020-08-22 03:01:52 +00:00
|
|
|
|
{
|
2021-02-21 16:38:16 +00:00
|
|
|
|
std::string language(GetStringSettingValue("Main", "Language", ""));
|
|
|
|
|
|
2020-08-22 03:01:52 +00:00
|
|
|
|
std::string path;
|
|
|
|
|
const ImWchar* range = nullptr;
|
|
|
|
|
#ifdef WIN32
|
2020-12-17 02:02:19 +00:00
|
|
|
|
if (language == "ja")
|
2020-08-22 03:01:52 +00:00
|
|
|
|
{
|
|
|
|
|
path = GetFontPath("msgothic.ttc");
|
|
|
|
|
range = ImGui::GetIO().Fonts->GetGlyphRangesJapanese();
|
|
|
|
|
}
|
|
|
|
|
else if (language == "zh-cn")
|
|
|
|
|
{
|
|
|
|
|
path = GetFontPath("msyh.ttc");
|
|
|
|
|
range = ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (!path.empty())
|
2021-02-21 16:38:16 +00:00
|
|
|
|
ImGuiFullscreen::SetFontFilename(std::move(path));
|
|
|
|
|
if (range)
|
|
|
|
|
ImGuiFullscreen::SetFontGlyphRanges(range);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::setImGuiKeyMap()
|
|
|
|
|
{
|
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
|
io.KeyMap[ImGuiKey_Tab] = Qt::Key_Tab & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_LeftArrow] = Qt::Key_Left & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_RightArrow] = Qt::Key_Right & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_UpArrow] = Qt::Key_Up & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_DownArrow] = Qt::Key_Down & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_PageUp] = Qt::Key_PageUp & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_PageDown] = Qt::Key_PageDown & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_Home] = Qt::Key_Home & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_End] = Qt::Key_End & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_Insert] = Qt::Key_Insert & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_Delete] = Qt::Key_Delete & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_Backspace] = Qt::Key_Backspace & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_Space] = Qt::Key_Space & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_Enter] = Qt::Key_Enter & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_Escape] = Qt::Key_Escape & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_A] = Qt::Key_A & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_C] = Qt::Key_C & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_V] = Qt::Key_V & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_X] = Qt::Key_X & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_Y] = Qt::Key_Y & IMGUI_KEY_MASK;
|
|
|
|
|
io.KeyMap[ImGuiKey_Z] = Qt::Key_Z & IMGUI_KEY_MASK;
|
2020-06-29 16:46:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 03:01:52 +00:00
|
|
|
|
TinyString QtHostInterface::TranslateString(const char* context, const char* str) const
|
|
|
|
|
{
|
|
|
|
|
const QString translated(m_translator->translate(context, str));
|
|
|
|
|
if (translated.isEmpty())
|
|
|
|
|
return TinyString(str);
|
|
|
|
|
|
|
|
|
|
return TinyString(translated.toUtf8().constData());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string QtHostInterface::TranslateStdString(const char* context, const char* str) const
|
|
|
|
|
{
|
|
|
|
|
const QString translated(m_translator->translate(context, str));
|
|
|
|
|
if (translated.isEmpty())
|
|
|
|
|
return std::string(str);
|
|
|
|
|
|
|
|
|
|
return translated.toStdString();
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-31 06:17:17 +00:00
|
|
|
|
QtHostInterface::Thread::Thread(QtHostInterface* parent) : QThread(parent), m_parent(parent) {}
|
|
|
|
|
|
|
|
|
|
QtHostInterface::Thread::~Thread() = default;
|
|
|
|
|
|
|
|
|
|
void QtHostInterface::Thread::run()
|
|
|
|
|
{
|
|
|
|
|
m_parent->threadEntryPoint();
|
|
|
|
|
}
|
2020-04-05 12:59:06 +00:00
|
|
|
|
|
|
|
|
|
void QtHostInterface::Thread::setInitResult(bool result)
|
|
|
|
|
{
|
|
|
|
|
m_init_result.store(result);
|
|
|
|
|
m_init_event.Signal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtHostInterface::Thread::waitForInit()
|
|
|
|
|
{
|
2020-07-05 16:47:13 +00:00
|
|
|
|
while (!m_init_event.TryWait(100))
|
|
|
|
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
|
|
|
|
2020-04-05 12:59:06 +00:00
|
|
|
|
return m_init_result.load();
|
|
|
|
|
}
|