2019-12-31 06:17:17 +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"
|
|
|
|
#include "common/log.h"
|
|
|
|
#include "common/string_util.h"
|
2020-01-02 06:13:03 +00:00
|
|
|
#include "core/controller.h"
|
2019-12-31 06:17:17 +00:00
|
|
|
#include "core/game_list.h"
|
|
|
|
#include "core/gpu.h"
|
|
|
|
#include "core/system.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"
|
2019-12-31 06:17:17 +00:00
|
|
|
#include "qtsettingsinterface.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-02-15 15:14:53 +00:00
|
|
|
#include <QtCore/QTimer>
|
2020-02-15 15:14:04 +00:00
|
|
|
#include <QtWidgets/QMenu>
|
2019-12-31 06:17:17 +00:00
|
|
|
#include <QtWidgets/QMessageBox>
|
|
|
|
#include <memory>
|
|
|
|
Log_SetChannel(QtHostInterface);
|
|
|
|
|
2020-01-07 05:54:37 +00:00
|
|
|
#ifdef WIN32
|
|
|
|
#include "d3d11displaywindow.h"
|
|
|
|
#endif
|
|
|
|
|
2019-12-31 06:17:17 +00:00
|
|
|
QtHostInterface::QtHostInterface(QObject* parent)
|
2020-01-24 04:51:03 +00:00
|
|
|
: QObject(parent), HostInterface(), m_qsettings(QString::fromStdString(GetSettingsFileName()), QSettings::IniFormat)
|
2019-12-31 06:17:17 +00:00
|
|
|
{
|
|
|
|
checkSettings();
|
2020-01-24 04:51:07 +00:00
|
|
|
refreshGameList();
|
2019-12-31 06:17:17 +00:00
|
|
|
createThread();
|
|
|
|
}
|
|
|
|
|
|
|
|
QtHostInterface::~QtHostInterface()
|
|
|
|
{
|
2020-01-02 07:45:25 +00:00
|
|
|
Assert(!m_display_window);
|
2019-12-31 06:17:17 +00:00
|
|
|
stopThread();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::ReportError(const char* message)
|
|
|
|
{
|
2020-01-24 04:51:30 +00:00
|
|
|
HostInterface::ReportError(message);
|
|
|
|
|
|
|
|
emit errorReported(QString::fromLocal8Bit(message));
|
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);
|
|
|
|
|
|
|
|
emit messageReported(QString::fromLocal8Bit(message));
|
2019-12-31 06:17:17 +00:00
|
|
|
}
|
|
|
|
|
2020-02-26 09:25:57 +00:00
|
|
|
bool QtHostInterface::ConfirmMessage(const char* message)
|
|
|
|
{
|
|
|
|
return messageConfirmed(QString::fromLocal8Bit(message));
|
|
|
|
}
|
|
|
|
|
2019-12-31 06:17:17 +00:00
|
|
|
void QtHostInterface::setDefaultSettings()
|
|
|
|
{
|
2020-02-09 13:16:25 +00:00
|
|
|
HostInterface::UpdateSettings([this]() { HostInterface::SetDefaultSettings(); });
|
2020-01-02 06:13:03 +00:00
|
|
|
|
|
|
|
// default input settings for Qt
|
2020-01-24 04:52:04 +00:00
|
|
|
std::lock_guard<std::mutex> guard(m_qsettings_mutex);
|
2020-01-02 06:13:03 +00:00
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonUp"), QStringLiteral("Keyboard/W"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonDown"), QStringLiteral("Keyboard/S"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonLeft"), QStringLiteral("Keyboard/A"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonRight"), QStringLiteral("Keyboard/D"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonSelect"), QStringLiteral("Keyboard/Backspace"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonStart"), QStringLiteral("Keyboard/Return"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonTriangle"), QStringLiteral("Keyboard/8"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonCross"), QStringLiteral("Keyboard/2"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonSquare"), QStringLiteral("Keyboard/4"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonCircle"), QStringLiteral("Keyboard/6"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonL1"), QStringLiteral("Keyboard/Q"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonL2"), QStringLiteral("Keyboard/1"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonR1"), QStringLiteral("Keyboard/E"));
|
|
|
|
m_qsettings.setValue(QStringLiteral("Controller1/ButtonR2"), QStringLiteral("Keyboard/3"));
|
|
|
|
|
2020-01-24 04:49:49 +00:00
|
|
|
updateQSettingsFromCoreSettings();
|
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:49 +00:00
|
|
|
QVariant QtHostInterface::getSettingValue(const QString& name, const QVariant& default_value)
|
2020-01-24 04:49:49 +00:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> guard(m_qsettings_mutex);
|
2020-02-15 15:14:49 +00:00
|
|
|
return m_qsettings.value(name, default_value);
|
2020-01-24 04:49:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::putSettingValue(const QString& name, const QVariant& value)
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> guard(m_qsettings_mutex);
|
|
|
|
m_qsettings.setValue(name, value);
|
2020-01-01 04:01:58 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 04:49:49 +00:00
|
|
|
void QtHostInterface::removeSettingValue(const QString& name)
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> guard(m_qsettings_mutex);
|
|
|
|
m_qsettings.remove(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::updateQSettingsFromCoreSettings()
|
2020-01-01 04:01:58 +00:00
|
|
|
{
|
|
|
|
QtSettingsInterface si(m_qsettings);
|
2019-12-31 06:17:17 +00:00
|
|
|
m_settings.Save(si);
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::applySettings()
|
|
|
|
{
|
2020-01-24 04:49:49 +00:00
|
|
|
if (!isOnWorkerThread())
|
|
|
|
{
|
|
|
|
QMetaObject::invokeMethod(this, "applySettings", Qt::QueuedConnection);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-01-24 04:51:52 +00:00
|
|
|
UpdateSettings([this]() {
|
2020-01-24 04:49:49 +00:00
|
|
|
std::lock_guard<std::mutex> guard(m_qsettings_mutex);
|
|
|
|
QtSettingsInterface si(m_qsettings);
|
|
|
|
m_settings.Load(si);
|
2020-01-24 04:51:52 +00:00
|
|
|
});
|
2019-12-31 06:17:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::checkSettings()
|
|
|
|
{
|
|
|
|
const QSettings::Status settings_status = m_qsettings.status();
|
|
|
|
if (settings_status != QSettings::NoError)
|
|
|
|
m_qsettings.clear();
|
|
|
|
|
|
|
|
const QString settings_version_key = QStringLiteral("General/SettingsVersion");
|
|
|
|
const int expected_version = 1;
|
|
|
|
const QVariant settings_version_var = m_qsettings.value(settings_version_key);
|
|
|
|
bool settings_version_okay;
|
|
|
|
int settings_version = settings_version_var.toInt(&settings_version_okay);
|
|
|
|
if (!settings_version_okay)
|
|
|
|
settings_version = 0;
|
|
|
|
if (settings_version != expected_version)
|
|
|
|
{
|
|
|
|
Log_WarningPrintf("Settings version %d does not match expected version %d, resetting", settings_version,
|
|
|
|
expected_version);
|
|
|
|
m_qsettings.clear();
|
|
|
|
m_qsettings.setValue(settings_version_key, expected_version);
|
|
|
|
setDefaultSettings();
|
|
|
|
}
|
2020-01-01 04:01:58 +00:00
|
|
|
|
2020-01-24 04:49:49 +00:00
|
|
|
// initial setting init - we don't do this locked since the thread hasn't been created yet
|
|
|
|
QtSettingsInterface si(m_qsettings);
|
|
|
|
m_settings.Load(si);
|
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-01-24 04:50:46 +00:00
|
|
|
std::lock_guard<std::mutex> lock(m_qsettings_mutex);
|
2019-12-31 06:17:17 +00:00
|
|
|
QtSettingsInterface si(m_qsettings);
|
2020-01-24 04:51:09 +00:00
|
|
|
m_game_list->SetSearchDirectoriesFromSettings(si);
|
2020-01-08 03:37:43 +00:00
|
|
|
m_game_list->Refresh(invalidate_cache, invalidate_database);
|
2019-12-31 06:17:17 +00:00
|
|
|
emit gameListRefreshed();
|
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
QtDisplayWindow* QtHostInterface::createDisplayWindow()
|
2019-12-31 06:17:17 +00:00
|
|
|
{
|
2020-02-15 15:14:28 +00:00
|
|
|
Assert(!m_display_window);
|
|
|
|
|
2020-01-07 05:54:37 +00:00
|
|
|
#ifdef WIN32
|
2020-01-07 08:55:36 +00:00
|
|
|
if (m_settings.gpu_renderer == GPURenderer::HardwareOpenGL)
|
|
|
|
m_display_window = new OpenGLDisplayWindow(this, nullptr);
|
|
|
|
else
|
|
|
|
m_display_window = new D3D11DisplayWindow(this, nullptr);
|
2020-01-07 05:54:37 +00:00
|
|
|
#else
|
2020-01-02 07:45:25 +00:00
|
|
|
m_display_window = new OpenGLDisplayWindow(this, nullptr);
|
2020-01-07 05:54:37 +00:00
|
|
|
#endif
|
2020-01-02 09:14:16 +00:00
|
|
|
connect(m_display_window, &QtDisplayWindow::windowResizedEvent, this, &QtHostInterface::onDisplayWindowResized);
|
2020-02-15 15:14:28 +00:00
|
|
|
return m_display_window;
|
2019-12-31 06:17:17 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:30 +00:00
|
|
|
void QtHostInterface::bootSystemFromFile(const QString& filename)
|
2020-01-24 04:49:51 +00:00
|
|
|
{
|
2020-02-15 15:14:28 +00:00
|
|
|
if (!isOnWorkerThread())
|
|
|
|
{
|
2020-02-15 15:14:30 +00:00
|
|
|
QMetaObject::invokeMethod(this, "bootSystemFromFile", Qt::QueuedConnection, Q_ARG(const QString&, filename));
|
2020-02-15 15:14:28 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-01-24 04:49:51 +00:00
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
HostInterface::BootSystemFromFile(filename.toStdString().c_str());
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (filename.isEmpty())
|
|
|
|
HostInterface::ResumeSystemFromMostRecentState();
|
|
|
|
else
|
|
|
|
HostInterface::ResumeSystemFromState(filename.toStdString().c_str(), boot_on_failure);
|
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
void QtHostInterface::bootSystemFromBIOS()
|
2019-12-31 06:17:17 +00:00
|
|
|
{
|
2020-02-15 15:14:28 +00:00
|
|
|
if (!isOnWorkerThread())
|
2019-12-31 06:17:17 +00:00
|
|
|
{
|
2020-02-15 15:14:28 +00:00
|
|
|
QMetaObject::invokeMethod(this, "bootSystemFromBIOS", Qt::QueuedConnection);
|
2019-12-31 06:17:17 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
HostInterface::BootSystemFromBIOS();
|
2019-12-31 06:17:17 +00:00
|
|
|
}
|
|
|
|
|
2020-01-02 06:13:03 +00:00
|
|
|
void QtHostInterface::handleKeyEvent(int key, bool pressed)
|
|
|
|
{
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
{
|
|
|
|
QMetaObject::invokeMethod(this, "doHandleKeyEvent", Qt::QueuedConnection, Q_ARG(int, key), Q_ARG(bool, pressed));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
doHandleKeyEvent(key, pressed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::doHandleKeyEvent(int key, bool pressed)
|
|
|
|
{
|
|
|
|
const auto iter = m_keyboard_input_handlers.find(key);
|
|
|
|
if (iter == m_keyboard_input_handlers.end())
|
|
|
|
return;
|
|
|
|
|
|
|
|
iter->second(pressed);
|
|
|
|
}
|
|
|
|
|
2020-01-02 09:14:16 +00:00
|
|
|
void QtHostInterface::onDisplayWindowResized(int width, int height)
|
|
|
|
{
|
2020-02-15 15:14:56 +00:00
|
|
|
// this can be null if it was destroyed and the main thread is late catching up
|
|
|
|
if (m_display_window)
|
|
|
|
m_display_window->onWindowResized(width, height);
|
2020-01-02 09:14:16 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
bool QtHostInterface::AcquireHostDisplay()
|
2020-01-24 04:51:52 +00:00
|
|
|
{
|
2020-02-15 15:14:28 +00:00
|
|
|
DebugAssert(!m_display_window);
|
|
|
|
|
|
|
|
emit createDisplayWindowRequested(m_worker_thread, m_settings.gpu_use_debug_device);
|
|
|
|
if (!m_display_window->hasDeviceContext())
|
2020-01-24 04:51:52 +00:00
|
|
|
{
|
2020-02-15 15:14:28 +00:00
|
|
|
m_display_window = nullptr;
|
|
|
|
emit destroyDisplayWindowRequested();
|
|
|
|
return false;
|
|
|
|
}
|
2020-01-24 04:51:52 +00:00
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
if (!m_display_window->initializeDeviceContext(m_settings.gpu_use_debug_device))
|
|
|
|
{
|
2020-01-24 04:51:52 +00:00
|
|
|
m_display_window->destroyDeviceContext();
|
2020-02-15 15:14:28 +00:00
|
|
|
m_display_window = nullptr;
|
|
|
|
emit destroyDisplayWindowRequested();
|
|
|
|
return false;
|
2020-01-24 04:51:52 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
m_display = m_display_window->getHostDisplayInterface();
|
|
|
|
return true;
|
|
|
|
}
|
2020-01-24 04:51:52 +00:00
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
void QtHostInterface::ReleaseHostDisplay()
|
|
|
|
{
|
|
|
|
DebugAssert(m_display_window && m_display == m_display_window->getHostDisplayInterface());
|
|
|
|
m_display = nullptr;
|
|
|
|
m_display_window->destroyDeviceContext();
|
|
|
|
m_display_window = nullptr;
|
|
|
|
emit destroyDisplayWindowRequested();
|
|
|
|
}
|
2020-01-24 04:51:52 +00:00
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
std::unique_ptr<AudioStream> QtHostInterface::CreateAudioStream(AudioBackend backend)
|
|
|
|
{
|
|
|
|
switch (backend)
|
|
|
|
{
|
|
|
|
case AudioBackend::Null:
|
|
|
|
return AudioStream::CreateNullAudioStream();
|
2020-01-24 04:51:52 +00:00
|
|
|
|
2020-02-15 15:14:35 +00:00
|
|
|
case AudioBackend::Cubeb:
|
|
|
|
return AudioStream::CreateCubebAudioStream();
|
|
|
|
|
2020-02-25 13:40:46 +00:00
|
|
|
case AudioBackend::SDL:
|
|
|
|
return SDLAudioStream::Create();
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
default:
|
|
|
|
return nullptr;
|
2020-02-09 13:16:25 +00:00
|
|
|
}
|
2020-01-24 04:51:52 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:28 +00:00
|
|
|
void QtHostInterface::OnSystemCreated()
|
|
|
|
{
|
|
|
|
HostInterface::OnSystemCreated();
|
|
|
|
|
|
|
|
wakeThread();
|
2020-02-15 15:14:53 +00:00
|
|
|
destroyBackgroundControllerPollTimer();
|
2020-02-15 15:14:28 +00:00
|
|
|
|
|
|
|
emit emulationStarted();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::OnSystemPaused(bool paused)
|
|
|
|
{
|
|
|
|
HostInterface::OnSystemPaused(paused);
|
|
|
|
|
|
|
|
if (!paused)
|
|
|
|
wakeThread();
|
|
|
|
|
|
|
|
emit emulationPaused(paused);
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::OnSystemDestroyed()
|
|
|
|
{
|
|
|
|
HostInterface::OnSystemDestroyed();
|
|
|
|
|
2020-02-15 15:14:53 +00:00
|
|
|
if (m_background_controller_polling_enable_count > 0)
|
|
|
|
createBackgroundControllerPollTimer();
|
|
|
|
|
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-02-09 13:16:25 +00:00
|
|
|
DebugAssert(m_system);
|
|
|
|
emit systemPerformanceCountersUpdated(m_system->GetEmulationSpeed(), m_system->GetFPS(), m_system->GetVPS(),
|
|
|
|
m_system->GetAverageFrameTime(), m_system->GetWorstFrameTime());
|
2020-01-24 04:49:47 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 04:51:05 +00:00
|
|
|
void QtHostInterface::OnRunningGameChanged()
|
2020-01-24 04:50:46 +00:00
|
|
|
{
|
2020-01-24 04:51:05 +00:00
|
|
|
HostInterface::OnRunningGameChanged();
|
2020-01-24 04:50:46 +00:00
|
|
|
|
2020-01-24 04:51:05 +00:00
|
|
|
if (m_system)
|
|
|
|
{
|
|
|
|
emit runningGameChanged(QString::fromStdString(m_system->GetRunningPath()),
|
|
|
|
QString::fromStdString(m_system->GetRunningCode()),
|
|
|
|
QString::fromStdString(m_system->GetRunningTitle()));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
emit runningGameChanged(QString(), QString(), QString());
|
|
|
|
}
|
2020-01-24 04:50:46 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:44 +00:00
|
|
|
void QtHostInterface::OnControllerTypeChanged(u32 slot)
|
|
|
|
{
|
|
|
|
HostInterface::OnControllerTypeChanged(slot);
|
|
|
|
|
|
|
|
updateInputMap();
|
|
|
|
}
|
|
|
|
|
2020-01-02 06:13:03 +00:00
|
|
|
void QtHostInterface::updateInputMap()
|
|
|
|
{
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
{
|
|
|
|
QMetaObject::invokeMethod(this, "doUpdateInputMap", Qt::QueuedConnection);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
doUpdateInputMap();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::doUpdateInputMap()
|
|
|
|
{
|
|
|
|
m_keyboard_input_handlers.clear();
|
2020-02-15 15:14:53 +00:00
|
|
|
g_sdl_controller_interface.ClearControllerBindings();
|
2020-01-02 06:13:03 +00:00
|
|
|
|
2020-02-15 15:14:53 +00:00
|
|
|
std::lock_guard<std::mutex> lock(m_qsettings_mutex);
|
2020-01-05 02:46:03 +00:00
|
|
|
updateControllerInputMap();
|
|
|
|
updateHotkeyInputMap();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::updateControllerInputMap()
|
|
|
|
{
|
2020-01-02 06:13:03 +00:00
|
|
|
for (u32 controller_index = 0; controller_index < 2; controller_index++)
|
|
|
|
{
|
|
|
|
const ControllerType ctype = m_settings.controller_types[controller_index];
|
|
|
|
if (ctype == ControllerType::None)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const auto button_names = Controller::GetButtonNames(ctype);
|
2020-02-13 07:52:30 +00:00
|
|
|
for (const auto& it : button_names)
|
2020-01-02 06:13:03 +00:00
|
|
|
{
|
2020-02-13 07:52:30 +00:00
|
|
|
const std::string& button_name = it.first;
|
|
|
|
const s32 button_code = it.second;
|
|
|
|
|
2020-01-02 06:13:03 +00:00
|
|
|
QVariant var = m_qsettings.value(
|
|
|
|
QStringLiteral("Controller%1/Button%2").arg(controller_index + 1).arg(QString::fromStdString(button_name)));
|
|
|
|
if (!var.isValid())
|
|
|
|
continue;
|
|
|
|
|
2020-01-05 02:46:03 +00:00
|
|
|
addButtonToInputMap(var.toString(), [this, controller_index, button_code](bool pressed) {
|
2020-01-02 06:13:03 +00:00
|
|
|
if (!m_system)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Controller* controller = m_system->GetController(controller_index);
|
|
|
|
if (controller)
|
|
|
|
controller->SetButtonState(button_code, pressed);
|
2020-01-05 02:46:03 +00:00
|
|
|
});
|
|
|
|
}
|
2020-02-17 15:06:28 +00:00
|
|
|
|
|
|
|
const auto axis_names = Controller::GetAxisNames(ctype);
|
|
|
|
for (const auto& it : axis_names)
|
|
|
|
{
|
|
|
|
const std::string& axis_name = it.first;
|
|
|
|
const s32 axis_code = it.second;
|
|
|
|
|
|
|
|
QVariant var = m_qsettings.value(
|
|
|
|
QStringLiteral("Controller%1/Axis%2").arg(controller_index + 1).arg(QString::fromStdString(axis_name)));
|
|
|
|
if (!var.isValid())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
addAxisToInputMap(var.toString(), [this, controller_index, axis_code](float value) {
|
|
|
|
if (!m_system)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Controller* controller = m_system->GetController(controller_index);
|
|
|
|
if (controller)
|
|
|
|
controller->SetAxisState(axis_code, value);
|
|
|
|
});
|
|
|
|
}
|
2020-01-05 02:46:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<QtHostInterface::HotkeyInfo> QtHostInterface::getHotkeyList() const
|
|
|
|
{
|
|
|
|
std::vector<HotkeyInfo> hotkeys = {
|
|
|
|
{QStringLiteral("FastForward"), QStringLiteral("Toggle Fast Forward"), QStringLiteral("General")},
|
|
|
|
{QStringLiteral("Fullscreen"), QStringLiteral("Toggle Fullscreen"), QStringLiteral("General")},
|
2020-01-24 04:51:55 +00:00
|
|
|
{QStringLiteral("Pause"), QStringLiteral("Toggle Pause"), QStringLiteral("General")},
|
|
|
|
{QStringLiteral("ToggleSoftwareRendering"), QStringLiteral("Toggle Software Rendering"),
|
|
|
|
QStringLiteral("Graphics")},
|
2020-01-24 04:51:57 +00:00
|
|
|
{QStringLiteral("IncreaseResolutionScale"), QStringLiteral("Increase Resolution Scale"),
|
|
|
|
QStringLiteral("Graphics")},
|
|
|
|
{QStringLiteral("DecreaseResolutionScale"), QStringLiteral("Decrease Resolution Scale"),
|
|
|
|
QStringLiteral("Graphics")}};
|
2020-01-05 02:46:03 +00:00
|
|
|
|
2020-02-15 15:14:04 +00:00
|
|
|
for (u32 global_i = 0; global_i < 2; global_i++)
|
2020-01-05 02:46:03 +00:00
|
|
|
{
|
2020-02-15 15:14:04 +00:00
|
|
|
const bool global = ConvertToBoolUnchecked(global_i);
|
|
|
|
const u32 count = global ? GLOBAL_SAVE_STATE_SLOTS : PER_GAME_SAVE_STATE_SLOTS;
|
|
|
|
for (u32 i = 1; i <= count; i++)
|
|
|
|
{
|
|
|
|
hotkeys.push_back({QStringLiteral("Load%1State%2").arg(global ? "Global" : "Game").arg(i),
|
|
|
|
QStringLiteral("Load %1 State %2").arg(global ? tr("Global") : tr("Game")).arg(i),
|
|
|
|
QStringLiteral("Save States")});
|
|
|
|
}
|
|
|
|
for (u32 slot = 1; slot <= count; slot++)
|
|
|
|
{
|
|
|
|
hotkeys.push_back({QStringLiteral("Save%1State%2").arg(global ? "Global" : "Game").arg(slot),
|
|
|
|
QStringLiteral("Save %1 State %2").arg(global ? tr("Global") : tr("Game")).arg(slot),
|
|
|
|
QStringLiteral("Save States")});
|
|
|
|
}
|
2020-01-05 02:46:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return hotkeys;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::updateHotkeyInputMap()
|
|
|
|
{
|
|
|
|
auto hk = [this](const QString& hotkey_name, InputButtonHandler handler) {
|
|
|
|
QVariant var = m_qsettings.value(QStringLiteral("Hotkeys/%1").arg(hotkey_name));
|
|
|
|
if (!var.isValid())
|
|
|
|
return;
|
|
|
|
|
|
|
|
addButtonToInputMap(var.toString(), std::move(handler));
|
|
|
|
};
|
|
|
|
|
|
|
|
hk(QStringLiteral("FastForward"), [this](bool pressed) {
|
|
|
|
m_speed_limiter_temp_disabled = pressed;
|
|
|
|
HostInterface::UpdateSpeedLimiterState();
|
|
|
|
});
|
|
|
|
|
|
|
|
hk(QStringLiteral("Fullscreen"), [this](bool pressed) {
|
|
|
|
if (!pressed)
|
2020-01-06 06:27:39 +00:00
|
|
|
emit toggleFullscreenRequested();
|
2020-01-05 02:46:03 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
hk(QStringLiteral("Pause"), [this](bool pressed) {
|
|
|
|
if (!pressed)
|
|
|
|
pauseSystem(!m_paused);
|
|
|
|
});
|
|
|
|
|
2020-01-24 04:51:55 +00:00
|
|
|
hk(QStringLiteral("ToggleSoftwareRendering"), [this](bool pressed) {
|
|
|
|
if (!pressed)
|
|
|
|
ToggleSoftwareRendering();
|
|
|
|
});
|
|
|
|
|
2020-01-24 04:51:57 +00:00
|
|
|
hk(QStringLiteral("IncreaseResolutionScale"), [this](bool pressed) {
|
|
|
|
if (!pressed)
|
|
|
|
ModifyResolutionScale(1);
|
|
|
|
});
|
|
|
|
|
|
|
|
hk(QStringLiteral("DecreaseResolutionScale"), [this](bool pressed) {
|
|
|
|
if (!pressed)
|
|
|
|
ModifyResolutionScale(-1);
|
|
|
|
});
|
|
|
|
|
2020-02-15 15:14:04 +00:00
|
|
|
for (u32 global_i = 0; global_i < 2; global_i++)
|
2020-01-05 02:46:03 +00:00
|
|
|
{
|
2020-02-15 15:14:04 +00:00
|
|
|
const bool global = ConvertToBoolUnchecked(global_i);
|
|
|
|
const u32 count = global ? GLOBAL_SAVE_STATE_SLOTS : PER_GAME_SAVE_STATE_SLOTS;
|
|
|
|
for (u32 slot = 1; slot <= count; slot++)
|
|
|
|
{
|
|
|
|
hk(QStringLiteral("Load%1State%2").arg(global ? "Global" : "Game").arg(slot), [this, global, slot](bool pressed) {
|
|
|
|
if (!pressed)
|
|
|
|
loadState(global, slot);
|
|
|
|
});
|
|
|
|
hk(QStringLiteral("Save%1State%2").arg(global ? "Global" : "Game").arg(slot), [this, global, slot](bool pressed) {
|
|
|
|
if (!pressed)
|
|
|
|
saveState(global, slot);
|
|
|
|
});
|
|
|
|
}
|
2020-01-05 02:46:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::addButtonToInputMap(const QString& binding, InputButtonHandler handler)
|
|
|
|
{
|
|
|
|
const QString device = binding.section('/', 0, 0);
|
|
|
|
const QString button = binding.section('/', 1, 1);
|
|
|
|
if (device == QStringLiteral("Keyboard"))
|
|
|
|
{
|
2020-01-06 05:14:47 +00:00
|
|
|
std::optional<int> key_id = QtUtils::ParseKeyString(button);
|
2020-01-05 02:46:03 +00:00
|
|
|
if (!key_id.has_value())
|
|
|
|
{
|
|
|
|
qWarning() << "Unknown keyboard key " << button;
|
|
|
|
return;
|
2020-01-02 06:13:03 +00:00
|
|
|
}
|
2020-01-05 02:46:03 +00:00
|
|
|
|
|
|
|
m_keyboard_input_handlers.emplace(key_id.value(), std::move(handler));
|
|
|
|
}
|
2020-02-15 15:14:53 +00:00
|
|
|
else if (device.startsWith(QStringLiteral("Controller")))
|
|
|
|
{
|
|
|
|
bool controller_index_okay;
|
|
|
|
const int controller_index = device.mid(10).toInt(&controller_index_okay);
|
|
|
|
if (!controller_index_okay || controller_index < 0)
|
|
|
|
{
|
|
|
|
qWarning() << "Malformed controller binding: " << binding;
|
|
|
|
return;
|
|
|
|
}
|
2020-02-17 15:06:28 +00:00
|
|
|
|
2020-02-15 15:14:53 +00:00
|
|
|
if (button.startsWith(QStringLiteral("Button")))
|
|
|
|
{
|
|
|
|
bool button_index_okay;
|
|
|
|
const int button_index = button.mid(6).toInt(&button_index_okay);
|
|
|
|
if (!button_index_okay ||
|
|
|
|
!g_sdl_controller_interface.BindControllerButton(controller_index, button_index, std::move(handler)))
|
|
|
|
{
|
|
|
|
qWarning() << "Failed to bind " << binding;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (button.startsWith(QStringLiteral("+Axis")) || button.startsWith(QStringLiteral("-Axis")))
|
|
|
|
{
|
|
|
|
bool axis_index_okay;
|
|
|
|
const int axis_index = button.mid(5).toInt(&axis_index_okay);
|
|
|
|
const bool positive = (button[0] == '+');
|
|
|
|
if (!axis_index_okay || !g_sdl_controller_interface.BindControllerAxisToButton(controller_index, axis_index,
|
|
|
|
positive, std::move(handler)))
|
|
|
|
{
|
|
|
|
qWarning() << "Failed to bind " << binding;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-05 02:46:03 +00:00
|
|
|
else
|
|
|
|
{
|
2020-02-15 15:14:53 +00:00
|
|
|
qWarning() << "Unknown input device: " << binding;
|
2020-01-05 02:46:03 +00:00
|
|
|
return;
|
2020-01-02 06:13:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-17 15:06:28 +00:00
|
|
|
void QtHostInterface::addAxisToInputMap(const QString& binding, InputAxisHandler handler)
|
|
|
|
{
|
|
|
|
const QString device = binding.section('/', 0, 0);
|
|
|
|
const QString axis = binding.section('/', 1, 1);
|
|
|
|
if (device.startsWith(QStringLiteral("Controller")))
|
|
|
|
{
|
|
|
|
bool controller_index_okay;
|
|
|
|
const int controller_index = device.mid(10).toInt(&controller_index_okay);
|
|
|
|
if (!controller_index_okay || controller_index < 0)
|
|
|
|
{
|
|
|
|
qWarning() << "Malformed controller binding: " << binding;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (axis.startsWith(QStringLiteral("Axis")))
|
|
|
|
{
|
|
|
|
bool axis_index_okay;
|
|
|
|
const int axis_index = axis.mid(4).toInt(&axis_index_okay);
|
|
|
|
if (!axis_index_okay ||
|
|
|
|
!g_sdl_controller_interface.BindControllerAxis(controller_index, axis_index, std::move(handler)))
|
|
|
|
{
|
|
|
|
qWarning() << "Failed to bind " << binding;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qWarning() << "Unknown input device: " << binding;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:49 +00:00
|
|
|
void QtHostInterface::powerOffSystem()
|
2019-12-31 06:17:17 +00:00
|
|
|
{
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
{
|
2020-02-15 15:14:49 +00:00
|
|
|
QMetaObject::invokeMethod(this, "powerOffSystem", Qt::QueuedConnection);
|
2019-12-31 06:17:17 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_system)
|
|
|
|
return;
|
|
|
|
|
2020-02-15 15:14:49 +00:00
|
|
|
if (m_settings.save_state_on_exit)
|
|
|
|
SaveResumeSaveState();
|
|
|
|
|
2020-01-24 04:50:54 +00:00
|
|
|
DestroySystem();
|
2019-12-31 06:17:17 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:49 +00:00
|
|
|
void QtHostInterface::synchronousPowerOffSystem()
|
|
|
|
{
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
QMetaObject::invokeMethod(this, "powerOffSystem", Qt::BlockingQueuedConnection);
|
|
|
|
else
|
|
|
|
powerOffSystem();
|
|
|
|
}
|
|
|
|
|
2019-12-31 06:17:17 +00:00
|
|
|
void QtHostInterface::resetSystem()
|
|
|
|
{
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
{
|
|
|
|
QMetaObject::invokeMethod(this, "resetSystem", Qt::QueuedConnection);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_system)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("resetSystem() called without system");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
HostInterface::ResetSystem();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::pauseSystem(bool paused)
|
|
|
|
{
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
{
|
|
|
|
QMetaObject::invokeMethod(this, "pauseSystem", Qt::QueuedConnection, Q_ARG(bool, paused));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_paused = paused;
|
2020-01-07 04:17:41 +00:00
|
|
|
m_audio_stream->PauseOutput(paused);
|
2020-01-10 03:28:13 +00:00
|
|
|
if (!paused)
|
|
|
|
wakeThread();
|
2019-12-31 06:17:17 +00:00
|
|
|
emit emulationPaused(paused);
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_system)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_system->InsertMedia(new_disc_filename.toStdString().c_str());
|
|
|
|
}
|
2019-12-31 06:17:17 +00:00
|
|
|
|
2020-02-15 15:14:04 +00:00
|
|
|
void QtHostInterface::populateSaveStateMenus(const char* game_code, QMenu* load_menu, QMenu* save_menu)
|
|
|
|
{
|
|
|
|
const std::vector<SaveStateInfo> available_states(GetAvailableSaveStates(game_code));
|
|
|
|
|
|
|
|
load_menu->clear();
|
|
|
|
if (!available_states.empty())
|
|
|
|
{
|
|
|
|
bool last_global = available_states.front().global;
|
2020-02-15 15:14:49 +00:00
|
|
|
s32 last_slot = available_states.front().slot;
|
2020-02-15 15:14:04 +00:00
|
|
|
for (const SaveStateInfo& ssi : available_states)
|
|
|
|
{
|
|
|
|
const s32 slot = ssi.slot;
|
|
|
|
const bool global = ssi.global;
|
|
|
|
const QDateTime timestamp(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(ssi.timestamp)));
|
2020-02-15 15:14:49 +00:00
|
|
|
const QString timestamp_str(timestamp.toString(Qt::SystemLocaleShortDate));
|
2020-02-15 15:14:04 +00:00
|
|
|
const QString path(QString::fromStdString(ssi.path));
|
|
|
|
|
2020-02-15 15:14:49 +00:00
|
|
|
QString title;
|
|
|
|
if (slot < 0)
|
|
|
|
title = tr("Resume Save (%1)").arg(timestamp_str);
|
|
|
|
else
|
|
|
|
title = tr("%1 Save %2 (%3)").arg(global ? tr("Global") : tr("Game")).arg(slot).arg(timestamp_str);
|
2020-02-15 15:14:04 +00:00
|
|
|
|
2020-02-15 15:14:49 +00:00
|
|
|
if (global != last_global || last_slot < 0)
|
2020-02-15 15:14:04 +00:00
|
|
|
load_menu->addSeparator();
|
2020-02-15 15:14:49 +00:00
|
|
|
|
|
|
|
last_global = global;
|
|
|
|
last_slot = slot;
|
2020-02-15 15:14:04 +00:00
|
|
|
|
|
|
|
QAction* action = load_menu->addAction(title);
|
|
|
|
connect(action, &QAction::triggered, [this, path]() { loadState(path); });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
save_menu->clear();
|
|
|
|
if (game_code && std::strlen(game_code) > 0)
|
|
|
|
{
|
|
|
|
for (s32 i = 1; i <= PER_GAME_SAVE_STATE_SLOTS; i++)
|
|
|
|
{
|
|
|
|
QAction* action = save_menu->addAction(tr("Game Save %1").arg(i));
|
2020-02-15 15:15:18 +00:00
|
|
|
connect(action, &QAction::triggered, [this, i]() { saveState(false, i); });
|
2020-02-15 15:14:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
save_menu->addSeparator();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (s32 i = 1; i <= GLOBAL_SAVE_STATE_SLOTS; i++)
|
|
|
|
{
|
|
|
|
QAction* action = save_menu->addAction(tr("Global Save %1").arg(i));
|
2020-02-15 15:15:18 +00:00
|
|
|
connect(action, &QAction::triggered, [this, i]() { saveState(true, i); });
|
2020-02-15 15:14:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-02-15 15:14:28 +00:00
|
|
|
LoadState(filename.toStdString().c_str());
|
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-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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_system)
|
2020-02-15 15:15:18 +00:00
|
|
|
{
|
2020-02-15 15:14:04 +00:00
|
|
|
SaveState(global, slot);
|
2020-02-15 15:15:18 +00:00
|
|
|
emit stateSaved(QString::fromStdString(m_system->GetRunningCode()), global, slot);
|
|
|
|
}
|
2020-02-15 15:14:04 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 15:14:53 +00:00
|
|
|
void QtHostInterface::enableBackgroundControllerPolling()
|
|
|
|
{
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
{
|
|
|
|
QMetaObject::invokeMethod(this, "enableBackgroundControllerPolling", Qt::BlockingQueuedConnection);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_background_controller_polling_enable_count++ > 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!m_system)
|
|
|
|
{
|
|
|
|
createBackgroundControllerPollTimer();
|
|
|
|
|
|
|
|
// drain the event queue so we don't get events late
|
|
|
|
g_sdl_controller_interface.PumpSDLEvents();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::disableBackgroundControllerPolling()
|
|
|
|
{
|
|
|
|
if (!isOnWorkerThread())
|
|
|
|
{
|
|
|
|
QMetaObject::invokeMethod(this, "disableBackgroundControllerPolling");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Assert(m_background_controller_polling_enable_count > 0);
|
|
|
|
if (--m_background_controller_polling_enable_count > 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!m_system)
|
|
|
|
destroyBackgroundControllerPollTimer();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::doBackgroundControllerPoll()
|
|
|
|
{
|
|
|
|
g_sdl_controller_interface.PumpSDLEvents();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::createBackgroundControllerPollTimer()
|
|
|
|
{
|
|
|
|
DebugAssert(!m_background_controller_polling_timer);
|
|
|
|
m_background_controller_polling_timer = new QTimer(this);
|
|
|
|
m_background_controller_polling_timer->setSingleShot(false);
|
|
|
|
m_background_controller_polling_timer->setTimerType(Qt::VeryCoarseTimer);
|
|
|
|
connect(m_background_controller_polling_timer, &QTimer::timeout, this, &QtHostInterface::doBackgroundControllerPoll);
|
|
|
|
m_background_controller_polling_timer->start(BACKGROUND_CONTROLLER_POLLING_INTERVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtHostInterface::destroyBackgroundControllerPollTimer()
|
|
|
|
{
|
|
|
|
delete m_background_controller_polling_timer;
|
|
|
|
m_background_controller_polling_timer = nullptr;
|
|
|
|
}
|
|
|
|
|
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-02-15 15:15:05 +00:00
|
|
|
g_sdl_controller_interface.Initialize(this);
|
2020-02-15 15:14:53 +00:00
|
|
|
g_sdl_controller_interface.PumpSDLEvents();
|
|
|
|
|
|
|
|
doUpdateInputMap();
|
|
|
|
|
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())
|
|
|
|
{
|
2020-01-10 03:28:13 +00:00
|
|
|
if (!m_system || m_paused)
|
2019-12-31 06:17:17 +00:00
|
|
|
{
|
|
|
|
// wait until we have a system before running
|
2020-01-10 03:28:13 +00:00
|
|
|
m_worker_thread_event_loop->exec();
|
2019-12-31 06:17:17 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-02-09 13:16:25 +00:00
|
|
|
m_system->RunFrame();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
2020-02-09 13:16:37 +00:00
|
|
|
m_system->GetGPU()->ResetGraphicsAPIState();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
2020-02-09 13:16:37 +00:00
|
|
|
DrawDebugWindows();
|
|
|
|
DrawOSDMessages();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
2020-02-09 13:16:37 +00:00
|
|
|
m_display->Render();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
2020-02-09 13:16:37 +00:00
|
|
|
m_system->GetGPU()->RestoreGraphicsAPIState();
|
2019-12-31 06:17:17 +00:00
|
|
|
|
2020-02-09 13:16:37 +00:00
|
|
|
if (m_speed_limiter_enabled)
|
|
|
|
m_system->Throttle();
|
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-02-15 15:14:53 +00:00
|
|
|
g_sdl_controller_interface.PumpSDLEvents();
|
2019-12-31 06:17:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
m_system.reset();
|
2020-01-11 03:49:52 +00:00
|
|
|
m_audio_stream.reset();
|
2020-02-15 15:14:53 +00:00
|
|
|
|
|
|
|
g_sdl_controller_interface.Shutdown();
|
|
|
|
|
2020-01-10 03:28:13 +00:00
|
|
|
delete m_worker_thread_event_loop;
|
|
|
|
m_worker_thread_event_loop = nullptr;
|
2019-12-31 06:17:17 +00:00
|
|
|
|
|
|
|
// move back to UI thread
|
|
|
|
moveToThread(m_original_thread);
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
}
|