diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 56994946d..e4e6f4e07 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -53,7 +53,6 @@ HostInterface::HostInterface() { SetUserDirectory(); CreateUserDirectorySubdirectories(); - SetDefaultSettings(); m_game_list = std::make_unique(); m_game_list->SetCacheFilename(GetGameListCacheFileName()); m_game_list->SetDatabaseFilename(GetGameListDatabaseFileName()); @@ -135,6 +134,17 @@ void HostInterface::ResetSystem() AddOSDMessage("System reset."); } +void HostInterface::PowerOffSystem() +{ + if (!m_system) + return; + + if (m_settings.save_state_on_exit) + SaveResumeSaveState(); + + DestroySystem(); +} + void HostInterface::DestroySystem() { if (!m_system) @@ -493,7 +503,11 @@ bool HostInterface::SaveState(bool global, s32 slot) } std::string save_path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(code.c_str(), slot); - return SaveState(save_path.c_str()); + if (!SaveState(save_path.c_str())) + return false; + + OnSystemStateSaved(global, slot); + return true; } bool HostInterface::ResumeSystemFromState(const char* filename, bool boot_on_failure) @@ -584,6 +598,8 @@ void HostInterface::OnSystemDestroyed() void HostInterface::OnSystemPerformanceCountersUpdated() {} +void HostInterface::OnSystemStateSaved(bool global, s32 slot) {} + void HostInterface::OnRunningGameChanged() {} void HostInterface::OnControllerTypeChanged(u32 slot) {} @@ -725,7 +741,7 @@ std::vector HostInterface::GetAvailableSaveStates( std::vector si; std::string path; - auto add_path = [&si](const std::string& path, s32 slot, bool global) { + auto add_path = [&si](std::string path, s32 slot, bool global) { FILESYSTEM_STAT_DATA sd; if (!FileSystem::StatFile(path.c_str(), &sd)) return; @@ -766,42 +782,67 @@ std::string HostInterface::GetMostRecentResumeSaveStatePath() const return std::move(most_recent->FileName); } -void HostInterface::SetDefaultSettings() +void HostInterface::CheckSettings(SettingsInterface& si) { - m_settings.region = ConsoleRegion::Auto; - m_settings.cpu_execution_mode = CPUExecutionMode::Interpreter; + const int settings_version = si.GetIntValue("Main", "SettingsVersion", -1); + if (settings_version == SETTINGS_VERSION) + return; - m_settings.emulation_speed = 1.0f; - m_settings.speed_limiter_enabled = true; - m_settings.increase_timer_resolution = true; - m_settings.start_paused = false; - m_settings.save_state_on_exit = true; - m_settings.confim_power_off = true; + // TODO: we probably should delete all the sections in the ini... + Log_WarningPrintf("Settings version %d does not match expected version %d, resetting", settings_version, + SETTINGS_VERSION); + si.Clear(); + si.SetIntValue("Main", "SettingsVersion", SETTINGS_VERSION); + SetDefaultSettings(si); +} - m_settings.gpu_renderer = Settings::DEFAULT_GPU_RENDERER; - m_settings.gpu_resolution_scale = 1; - m_settings.gpu_true_color = true; - m_settings.gpu_texture_filtering = false; - m_settings.gpu_force_progressive_scan = true; - m_settings.gpu_use_debug_device = false; - m_settings.display_linear_filtering = true; - m_settings.display_fullscreen = false; - m_settings.video_sync_enabled = true; +void HostInterface::SetDefaultSettings(SettingsInterface& si) +{ + si.SetStringValue("Console", "Region", Settings::GetConsoleRegionName(ConsoleRegion::Auto)); - m_settings.cdrom_read_thread = true; + si.SetFloatValue("Main", "EmulationSpeed", 1.0f); + si.SetBoolValue("Main", "SpeedLimiterEnabled", true); + si.SetBoolValue("Main", "IncreaseTimerResolution", true); + si.SetBoolValue("Main", "StartPaused", false); + si.SetBoolValue("Main", "SaveStateOnExit", true); + si.SetBoolValue("Main", "ConfirmPowerOff", true); - m_settings.audio_backend = AudioBackend::Cubeb; - m_settings.audio_sync_enabled = true; + si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(CPUExecutionMode::Interpreter)); - m_settings.bios_path = "bios/scph1001.bin"; - m_settings.bios_patch_tty_enable = false; - m_settings.bios_patch_fast_boot = false; + si.SetStringValue("GPU", "Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER)); + si.SetIntValue("GPU", "ResolutionScale", 1); + si.SetBoolValue("GPU", "TrueColor", true); + si.SetBoolValue("GPU", "TextureFiltering", false); + si.SetBoolValue("GPU", "ForceProgressiveScan", true); + si.SetBoolValue("GPU", "UseDebugDevice", false); - m_settings.controller_types[0] = ControllerType::DigitalController; - m_settings.controller_types[1] = ControllerType::None; + si.SetBoolValue("Display", "LinearFiltering", true); + si.SetBoolValue("Display", "Fullscreen", false); + si.SetBoolValue("Display", "VSync", true); - m_settings.memory_card_paths[0] = "memcards/shared_card_1.mcd"; - m_settings.memory_card_paths[1].clear(); + si.SetBoolValue("CDROM", "ReadThread", true); + + si.SetStringValue("Audio", "Backend", Settings::GetAudioBackendName(AudioBackend::Cubeb)); + si.SetBoolValue("Audio", "Sync", true); + + si.SetStringValue("BIOS", "Path", "bios/scph1001.bin"); + si.SetBoolValue("BIOS", "PatchTTYEnable", false); + si.SetBoolValue("BIOS", "PatchFastBoot", false); + + si.SetStringValue("Controller1", "Type", Settings::GetControllerTypeName(ControllerType::DigitalController)); + si.SetStringValue("Controller2", "Type", Settings::GetControllerTypeName(ControllerType::None)); + + si.SetStringValue("MemoryCards", "Card1Path", "memcards/shared_card_1.mcd"); + si.SetStringValue("MemoryCards", "Card2Path", ""); + + si.SetBoolValue("Debug", "ShowVRAM", false); + si.SetBoolValue("Debug", "DumpCPUToVRAMCopies", false); + si.SetBoolValue("Debug", "DumpVRAMToCPUCopies", false); + si.SetBoolValue("Debug", "ShowGPUState", false); + si.SetBoolValue("Debug", "ShowCDROMState", false); + si.SetBoolValue("Debug", "ShowSPUState", false); + si.SetBoolValue("Debug", "ShowTimersState", false); + si.SetBoolValue("Debug", "ShowMDECState", false); } void HostInterface::UpdateSettings(const std::function& apply_callback) diff --git a/src/core/host_interface.h b/src/core/host_interface.h index 41cf708d4..1def726c7 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -46,15 +46,17 @@ public: bool BootSystemFromBIOS(); void PauseSystem(bool paused); void ResetSystem(); + void PowerOffSystem(); void DestroySystem(); + /// Loads state from the specified filename. + bool LoadState(const char* filename); + /// Loads the current emulation state from file. Specifying a slot of -1 loads the "resume" game state. bool LoadState(bool global, s32 slot); - bool LoadState(const char* filename); /// Saves the current emulation state to a file. Specifying a slot of -1 saves the "resume" save state. bool SaveState(bool global, s32 slot); - bool SaveState(const char* filename); /// Loads the resume save state for the given game. Optionally boots the game anyway if loading fails. bool ResumeSystemFromState(const char* filename, bool boot_on_failure); @@ -91,12 +93,13 @@ public: protected: enum : u32 { + SETTINGS_VERSION = 2, AUDIO_SAMPLE_RATE = 44100, AUDIO_CHANNELS = 2, AUDIO_BUFFER_SIZE = 2048, AUDIO_BUFFERS = 2, PER_GAME_SAVE_STATE_SLOTS = 10, - GLOBAL_SAVE_STATE_SLOTS = 10 + GLOBAL_SAVE_STATE_SLOTS = 10, }; struct OSDMessage @@ -122,6 +125,7 @@ protected: virtual void OnSystemPaused(bool paused); virtual void OnSystemDestroyed(); virtual void OnSystemPerformanceCountersUpdated(); + virtual void OnSystemStateSaved(bool global, s32 slot); virtual void OnRunningGameChanged(); virtual void OnControllerTypeChanged(u32 slot); @@ -160,8 +164,11 @@ protected: /// Loads the BIOS image for the specified region. std::optional> GetBIOSImage(ConsoleRegion region); + /// Ensures the settings is valid and the correct version. If not, resets to defaults. + void CheckSettings(SettingsInterface& si); + /// Restores all settings to defaults. - void SetDefaultSettings(); + virtual void SetDefaultSettings(SettingsInterface& si); /// Applies new settings, updating internal state as needed. apply_callback should call m_settings.Load() after /// locking any required mutexes. @@ -202,4 +209,5 @@ protected: private: void CreateAudioStream(); + bool SaveState(const char* filename); }; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 111189ddd..133628b21 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -9,12 +9,12 @@ void Settings::Load(SettingsInterface& si) region = ParseConsoleRegionName(si.GetStringValue("Console", "Region", "NTSC-U").c_str()).value_or(ConsoleRegion::NTSC_U); - emulation_speed = si.GetFloatValue("General", "EmulationSpeed", 1.0f); - speed_limiter_enabled = si.GetBoolValue("General", "SpeedLimiterEnabled", true); - increase_timer_resolution = si.GetBoolValue("General", "IncreaseTimerResolution", true); - start_paused = si.GetBoolValue("General", "StartPaused", false); - save_state_on_exit = si.GetBoolValue("General", "SaveStateOnExit", true); - confim_power_off = si.GetBoolValue("General", "ConfirmPowerOff", true); + emulation_speed = si.GetFloatValue("Main", "EmulationSpeed", 1.0f); + speed_limiter_enabled = si.GetBoolValue("Main", "SpeedLimiterEnabled", true); + increase_timer_resolution = si.GetBoolValue("Main", "IncreaseTimerResolution", true); + start_paused = si.GetBoolValue("Mainal", "StartPaused", false); + save_state_on_exit = si.GetBoolValue("Main", "SaveStateOnExit", true); + confim_power_off = si.GetBoolValue("Main", "ConfirmPowerOff", true); cpu_execution_mode = ParseCPUExecutionMode(si.GetStringValue("CPU", "ExecutionMode", "Interpreter").c_str()) .value_or(CPUExecutionMode::Interpreter); @@ -63,12 +63,12 @@ void Settings::Save(SettingsInterface& si) const { si.SetStringValue("Console", "Region", GetConsoleRegionName(region)); - si.SetFloatValue("General", "EmulationSpeed", emulation_speed); - si.SetBoolValue("General", "SpeedLimiterEnabled", speed_limiter_enabled); - si.SetBoolValue("General", "IncreaseTimerResolution", increase_timer_resolution); - si.SetBoolValue("General", "StartPaused", start_paused); - si.SetBoolValue("General", "SaveStateOnExit", save_state_on_exit); - si.SetBoolValue("General", "ConfirmPowerOff", confim_power_off); + si.SetFloatValue("Main", "EmulationSpeed", emulation_speed); + si.SetBoolValue("Main", "SpeedLimiterEnabled", speed_limiter_enabled); + si.SetBoolValue("Main", "IncreaseTimerResolution", increase_timer_resolution); + si.SetBoolValue("Main", "StartPaused", start_paused); + si.SetBoolValue("Main", "SaveStateOnExit", save_state_on_exit); + si.SetBoolValue("Main", "ConfirmPowerOff", confim_power_off); si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode)); diff --git a/src/core/settings.h b/src/core/settings.h index 953c32dd1..fb1037196 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -9,6 +9,8 @@ class SettingsInterface { public: + virtual void Clear() = 0; + virtual int GetIntValue(const char* section, const char* key, int default_value = 0) = 0; virtual float GetFloatValue(const char* section, const char* key, float default_value = 0.0f) = 0; virtual bool GetBoolValue(const char* section, const char* key, bool default_value = false) = 0; diff --git a/src/duckstation-qt/consolesettingswidget.cpp b/src/duckstation-qt/consolesettingswidget.cpp index feb0cc768..d32f79b46 100644 --- a/src/duckstation-qt/consolesettingswidget.cpp +++ b/src/duckstation-qt/consolesettingswidget.cpp @@ -20,15 +20,14 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, m_ui.biosPath, "BIOS/Path"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableTTYOutput, "BIOS/PatchTTYEnable"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.fastBoot, "BIOS/PatchFastBoot"); - SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableSpeedLimiter, - "General/SpeedLimiterEnabled"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableSpeedLimiter, "Main/SpeedLimiterEnabled"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.increaseTimerResolution, - "General/IncreaseTimerResolution"); - SettingWidgetBinder::BindWidgetToNormalizedSetting(m_host_interface, m_ui.emulationSpeed, "General/EmulationSpeed", + "Main/IncreaseTimerResolution"); + SettingWidgetBinder::BindWidgetToNormalizedSetting(m_host_interface, m_ui.emulationSpeed, "Main/EmulationSpeed", 100.0f); - SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pauseOnStart, "General/StartPaused"); - SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.saveStateOnExit, "General/SaveStateOnExit"); - SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.confirmPowerOff, "General/ConfirmPowerOff"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pauseOnStart, "Main/StartPaused"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.saveStateOnExit, "Main/SaveStateOnExit"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.confirmPowerOff, "Main/ConfirmPowerOff"); SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.cpuExecutionMode, "CPU/ExecutionMode", &Settings::ParseCPUExecutionMode, &Settings::GetCPUExecutionModeName); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cdromReadThread, "CDROM/ReadThread"); diff --git a/src/duckstation-qt/hotkeysettingswidget.cpp b/src/duckstation-qt/hotkeysettingswidget.cpp index 41f4921a8..a952ac896 100644 --- a/src/duckstation-qt/hotkeysettingswidget.cpp +++ b/src/duckstation-qt/hotkeysettingswidget.cpp @@ -34,11 +34,12 @@ void HotkeySettingsWidget::createUi() void HotkeySettingsWidget::createButtons() { - std::vector hotkeys = m_host_interface->getHotkeyList(); - - for (const QtHostInterface::HotkeyInfo& hi : hotkeys) + const auto& hotkeys = m_host_interface->getHotkeyInfoList(); + for (const auto& hi : hotkeys) { - auto iter = m_categories.find(hi.category); + const auto category = QString::fromUtf8(hi.category); + + auto iter = m_categories.find(category); if (iter == m_categories.end()) { QScrollArea* scroll = new QScrollArea(m_tab_widget); @@ -48,20 +49,20 @@ void HotkeySettingsWidget::createButtons() layout->setContentsMargins(0, 0, 0, 0); vlayout->addLayout(layout); vlayout->addStretch(1); - iter = m_categories.insert(hi.category, Category{container, layout}); + iter = m_categories.insert(category, Category{container, layout}); scroll->setWidget(container); scroll->setWidgetResizable(true); scroll->setBackgroundRole(QPalette::Base); scroll->setFrameShape(QFrame::NoFrame); - m_tab_widget->addTab(scroll, hi.category); + m_tab_widget->addTab(scroll, category); } QWidget* container = iter->container; QGridLayout* layout = iter->layout; const int target_row = layout->count() / 2; - const QString setting_name = QStringLiteral("Hotkeys/%1").arg(hi.name); - layout->addWidget(new QLabel(hi.display_name, container), target_row, 0); + const QString setting_name = QStringLiteral("Hotkeys/%1").arg(hi.name.GetCharArray()); + layout->addWidget(new QLabel(QString::fromUtf8(hi.display_name), container), target_row, 0); layout->addWidget(new InputButtonBindingWidget(m_host_interface, setting_name, container), target_row, 1); } } diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 4a67c2d7d..0d4110ff3 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -45,7 +45,10 @@ void MainWindow::reportMessage(const QString& message) bool MainWindow::confirmMessage(const QString& message) { - return (QMessageBox::question(this, tr("DuckStation"), message) == QMessageBox::Yes); + const int result = QMessageBox::question(this, tr("DuckStation"), message); + focusDisplayWidget(); + + return (result == QMessageBox::Yes); } void MainWindow::createDisplayWindow(QThread* worker_thread, bool use_debug_device) diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 75a608767..51b0fc65f 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -27,9 +27,10 @@ Log_SetChannel(QtHostInterface); #endif QtHostInterface::QtHostInterface(QObject* parent) - : QObject(parent), HostInterface(), m_qsettings(QString::fromStdString(GetSettingsFileName()), QSettings::IniFormat) + : QObject(parent), CommonHostInterface(), + m_qsettings(QString::fromStdString(GetSettingsFileName()), QSettings::IniFormat) { - checkSettings(); + loadSettings(); refreshGameList(); createThread(); } @@ -59,34 +60,6 @@ bool QtHostInterface::ConfirmMessage(const char* message) return messageConfirmed(QString::fromLocal8Bit(message)); } -void QtHostInterface::setDefaultSettings() -{ - HostInterface::UpdateSettings([this]() { HostInterface::SetDefaultSettings(); }); - - // default input settings for Qt - std::lock_guard guard(m_qsettings_mutex); - 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")); - m_qsettings.setValue(QStringLiteral("Hotkeys/FastForward"), QStringLiteral("Keyboard/Tab")); - m_qsettings.setValue(QStringLiteral("Hotkeys/PowerOff"), QStringLiteral("Keyboard/Escape")); - m_qsettings.setValue(QStringLiteral("Hotkeys/TogglePause"), QStringLiteral("Keyboard/Pause")); - m_qsettings.setValue(QStringLiteral("Hotkeys/ToggleFullscreen"), QStringLiteral("Keyboard/Alt+Return")); - - updateQSettingsFromCoreSettings(); -} - QVariant QtHostInterface::getSettingValue(const QString& name, const QVariant& default_value) { std::lock_guard guard(m_qsettings_mutex); @@ -105,10 +78,18 @@ void QtHostInterface::removeSettingValue(const QString& name) m_qsettings.remove(name); } -void QtHostInterface::updateQSettingsFromCoreSettings() +void QtHostInterface::setDefaultSettings() { + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "setDefaultSettings", Qt::QueuedConnection); + return; + } + + std::lock_guard guard(m_qsettings_mutex); QtSettingsInterface si(m_qsettings); - m_settings.Save(si); + UpdateSettings([this, &si]() { m_settings.Load(si); }); + UpdateInputMap(si); } void QtHostInterface::applySettings() @@ -119,38 +100,28 @@ void QtHostInterface::applySettings() return; } - UpdateSettings([this]() { - std::lock_guard guard(m_qsettings_mutex); - QtSettingsInterface si(m_qsettings); - m_settings.Load(si); - }); + std::lock_guard guard(m_qsettings_mutex); + QtSettingsInterface si(m_qsettings); + UpdateSettings([this, &si]() { m_settings.Load(si); }); + UpdateInputMap(si); } -void QtHostInterface::checkSettings() +void QtHostInterface::loadSettings() { + // no need to lock here because the emu thread doesn't exist yet + QtSettingsInterface si(m_qsettings); + 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(); + SetDefaultSettings(si); } - // initial setting init - we don't do this locked since the thread hasn't been created yet - QtSettingsInterface si(m_qsettings); + CheckSettings(si); m_settings.Load(si); + + // input map update is done on the emu thread } void QtHostInterface::refreshGameList(bool invalidate_cache /* = false */, bool invalidate_database /* = false */) @@ -219,20 +190,11 @@ void QtHostInterface::handleKeyEvent(int key, bool pressed) { if (!isOnWorkerThread()) { - QMetaObject::invokeMethod(this, "doHandleKeyEvent", Qt::QueuedConnection, Q_ARG(int, key), Q_ARG(bool, pressed)); + QMetaObject::invokeMethod(this, "handleKeyEvent", 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); + HandleHostKeyEvent(key, pressed); } void QtHostInterface::onDisplayWindowResized(int width, int height) @@ -275,22 +237,24 @@ void QtHostInterface::ReleaseHostDisplay() emit destroyDisplayWindowRequested(); } -std::unique_ptr QtHostInterface::CreateAudioStream(AudioBackend backend) +void QtHostInterface::SetFullscreen(bool enabled) { - switch (backend) - { - case AudioBackend::Null: - return AudioStream::CreateNullAudioStream(); + emit setFullscreenRequested(enabled); +} - case AudioBackend::Cubeb: - return AudioStream::CreateCubebAudioStream(); +void QtHostInterface::ToggleFullscreen() +{ + emit toggleFullscreenRequested(); +} - case AudioBackend::SDL: - return SDLAudioStream::Create(); +std::optional QtHostInterface::GetHostKeyCode(const std::string_view key_code) const +{ + const std::optional code = + QtUtils::ParseKeyString(QString::fromUtf8(key_code.data(), static_cast(key_code.length()))); + if (!code) + return std::nullopt; - default: - return nullptr; - } + return static_cast(*code); } void QtHostInterface::OnSystemCreated() @@ -351,6 +315,11 @@ void QtHostInterface::OnRunningGameChanged() } } +void QtHostInterface::OnSystemStateSaved(bool global, s32 slot) +{ + emit stateSaved(QString::fromStdString(m_system->GetRunningCode()), global, slot); +} + void QtHostInterface::OnControllerTypeChanged(u32 slot) { HostInterface::OnControllerTypeChanged(slot); @@ -362,282 +331,13 @@ void QtHostInterface::updateInputMap() { if (!isOnWorkerThread()) { - QMetaObject::invokeMethod(this, "doUpdateInputMap", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "updateInputMap", Qt::QueuedConnection); return; } - doUpdateInputMap(); -} - -void QtHostInterface::doUpdateInputMap() -{ - m_keyboard_input_handlers.clear(); - g_sdl_controller_interface.ClearControllerBindings(); - std::lock_guard lock(m_qsettings_mutex); - updateControllerInputMap(); - updateHotkeyInputMap(); -} - -void QtHostInterface::updateControllerInputMap() -{ - 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); - for (const auto& it : button_names) - { - const std::string& button_name = it.first; - const s32 button_code = it.second; - - QVariant var = m_qsettings.value( - QStringLiteral("Controller%1/Button%2").arg(controller_index + 1).arg(QString::fromStdString(button_name))); - if (!var.isValid()) - continue; - - addButtonToInputMap(var.toString(), [this, controller_index, button_code](bool pressed) { - if (!m_system) - return; - - Controller* controller = m_system->GetController(controller_index); - if (controller) - controller->SetButtonState(button_code, pressed); - }); - } - - 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); - }); - } - } -} - -std::vector QtHostInterface::getHotkeyList() const -{ - std::vector hotkeys = { - {QStringLiteral("FastForward"), QStringLiteral("Toggle Fast Forward"), QStringLiteral("General")}, - {QStringLiteral("ToggleFullscreen"), QStringLiteral("Toggle Fullscreen"), QStringLiteral("General")}, - {QStringLiteral("TogglePause"), QStringLiteral("Toggle Pause"), QStringLiteral("General")}, - {QStringLiteral("PowerOff"), QStringLiteral("Power Off System"), QStringLiteral("General")}, - {QStringLiteral("ToggleSoftwareRendering"), QStringLiteral("Toggle Software Rendering"), - QStringLiteral("Graphics")}, - {QStringLiteral("IncreaseResolutionScale"), QStringLiteral("Increase Resolution Scale"), - QStringLiteral("Graphics")}, - {QStringLiteral("DecreaseResolutionScale"), QStringLiteral("Decrease Resolution Scale"), - QStringLiteral("Graphics")}}; - - for (u32 global_i = 0; global_i < 2; global_i++) - { - 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")}); - } - } - - 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("ToggleFullscreen"), [this](bool pressed) { - if (!pressed) - emit toggleFullscreenRequested(); - }); - - hk(QStringLiteral("TogglePause"), [this](bool pressed) { - if (!pressed) - pauseSystem(!m_paused); - }); - - hk(QStringLiteral("PowerOff"), [this](bool pressed) { - if (!pressed && m_system) - { - if (m_settings.confim_power_off) - { - emit setFullscreenRequested(false); - - QString confirmation_message = tr("Are you sure you want to stop emulation?"); - if (m_settings.save_state_on_exit) - { - confirmation_message += "\n\n"; - confirmation_message += tr("The current state will be saved."); - } - - if (!messageConfirmed(confirmation_message)) - { - if (m_settings.display_fullscreen) - emit setFullscreenRequested(true); - else - emit focusDisplayWidgetRequested(); - - m_system->ResetPerformanceCounters(); - return; - } - } - - powerOffSystem(); - } - }); - - hk(QStringLiteral("ToggleSoftwareRendering"), [this](bool pressed) { - if (!pressed) - ToggleSoftwareRendering(); - }); - - hk(QStringLiteral("IncreaseResolutionScale"), [this](bool pressed) { - if (!pressed) - ModifyResolutionScale(1); - }); - - hk(QStringLiteral("DecreaseResolutionScale"), [this](bool pressed) { - if (!pressed) - ModifyResolutionScale(-1); - }); - - for (u32 global_i = 0; global_i < 2; global_i++) - { - 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); - }); - } - } -} - -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")) - { - std::optional key_id = QtUtils::ParseKeyString(button); - if (!key_id.has_value()) - { - qWarning() << "Unknown keyboard key " << button; - return; - } - - m_keyboard_input_handlers.emplace(key_id.value(), std::move(handler)); - } - 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; - } - - 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; - } - } - } - else - { - qWarning() << "Unknown input device: " << binding; - return; - } -} - -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; - } + QtSettingsInterface si(m_qsettings); + UpdateInputMap(si); } void QtHostInterface::powerOffSystem() @@ -799,10 +499,7 @@ void QtHostInterface::saveState(bool global, qint32 slot, bool block_until_done } if (m_system) - { SaveState(global, slot); - emit stateSaved(QString::fromStdString(m_system->GetRunningCode()), global, slot); - } } void QtHostInterface::enableBackgroundControllerPolling() @@ -891,8 +588,7 @@ void QtHostInterface::threadEntryPoint() // set up controller interface and immediate poll to pick up the controller attached events g_sdl_controller_interface.Initialize(this); g_sdl_controller_interface.PumpSDLEvents(); - - doUpdateInputMap(); + updateInputMap(); // TODO: Event which flags the thread as ready while (!m_shutdown_flag.load()) diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 2bcd1ed61..2bbcf0607 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -1,5 +1,6 @@ #pragma once #include "core/host_interface.h" +#include "frontend-common/common_host_interface.h" #include "opengldisplaywindow.h" #include #include @@ -22,7 +23,7 @@ class QTimer; class GameList; -class QtHostInterface : public QObject, private HostInterface +class QtHostInterface : public QObject, private CommonHostInterface { Q_OBJECT @@ -34,8 +35,6 @@ public: void ReportMessage(const char* message) override; bool ConfirmMessage(const char* message) override; - void setDefaultSettings(); - /// Thread-safe QSettings access. QVariant getSettingValue(const QString& name, const QVariant& default_value = QVariant()); void putSettingValue(const QString& name, const QVariant& value); @@ -45,21 +44,12 @@ public: GameList* getGameList() { return m_game_list.get(); } void refreshGameList(bool invalidate_cache = false, bool invalidate_database = false); + const HotkeyInfoList& getHotkeyInfoList() const { return GetHotkeyInfoList(); } + bool isOnWorkerThread() const { return QThread::currentThread() == m_worker_thread; } QtDisplayWindow* createDisplayWindow(); - void updateInputMap(); - void handleKeyEvent(int key, bool pressed); - - struct HotkeyInfo - { - QString name; - QString display_name; - QString category; - }; - std::vector getHotkeyList() const; - void populateSaveStateMenus(const char* game_code, QMenu* load_menu, QMenu* save_menu); Q_SIGNALS: @@ -81,7 +71,10 @@ Q_SIGNALS: void runningGameChanged(const QString& filename, const QString& game_code, const QString& game_title); public Q_SLOTS: + void setDefaultSettings(); void applySettings(); + void updateInputMap(); + void handleKeyEvent(int key, bool pressed); void bootSystemFromFile(const QString& filename); void resumeSystemFromState(const QString& filename, bool boot_on_failure); void bootSystemFromBIOS(); @@ -103,21 +96,23 @@ public Q_SLOTS: private Q_SLOTS: void doStopThread(); - void doUpdateInputMap(); - void doHandleKeyEvent(int key, bool pressed); void onDisplayWindowResized(int width, int height); void doBackgroundControllerPoll(); protected: bool AcquireHostDisplay() override; void ReleaseHostDisplay() override; - std::unique_ptr CreateAudioStream(AudioBackend backend) override; + void SetFullscreen(bool enabled) override; + void ToggleFullscreen() override; + + std::optional GetHostKeyCode(const std::string_view key_code) const override; void OnSystemCreated() override; void OnSystemPaused(bool paused) override; void OnSystemDestroyed() override; void OnSystemPerformanceCountersUpdated() override; void OnRunningGameChanged() override; + void OnSystemStateSaved(bool global, s32 slot) override; void OnControllerTypeChanged(u32 slot) override; private: @@ -143,15 +138,10 @@ private: QtHostInterface* m_parent; }; - void checkSettings(); - void updateQSettingsFromCoreSettings(); + void loadSettings(); void createBackgroundControllerPollTimer(); void destroyBackgroundControllerPollTimer(); - void updateControllerInputMap(); - void updateHotkeyInputMap(); - void addButtonToInputMap(const QString& binding, InputButtonHandler handler); - void addAxisToInputMap(const QString& binding, InputAxisHandler handler); void createThread(); void stopThread(); void threadEntryPoint(); diff --git a/src/duckstation-qt/qtsettingsinterface.cpp b/src/duckstation-qt/qtsettingsinterface.cpp index 66aeefeb9..fb30c8585 100644 --- a/src/duckstation-qt/qtsettingsinterface.cpp +++ b/src/duckstation-qt/qtsettingsinterface.cpp @@ -11,6 +11,11 @@ QtSettingsInterface::QtSettingsInterface(QSettings& settings) : m_settings(setti QtSettingsInterface::~QtSettingsInterface() = default; +void QtSettingsInterface::Clear() +{ + m_settings.clear(); +} + int QtSettingsInterface::GetIntValue(const char* section, const char* key, int default_value /*= 0*/) { QVariant value = m_settings.value(GetFullKey(section, key)); diff --git a/src/duckstation-qt/qtsettingsinterface.h b/src/duckstation-qt/qtsettingsinterface.h index 15851e334..800e03ce9 100644 --- a/src/duckstation-qt/qtsettingsinterface.h +++ b/src/duckstation-qt/qtsettingsinterface.h @@ -9,6 +9,8 @@ public: QtSettingsInterface(QSettings& settings); ~QtSettingsInterface(); + void Clear() override; + int GetIntValue(const char* section, const char* key, int default_value = 0) override; float GetFloatValue(const char* section, const char* key, float default_value = 0.0f) override; bool GetBoolValue(const char* section, const char* key, bool default_value = false) override; diff --git a/src/duckstation-sdl/sdl_settings_interface.cpp b/src/duckstation-sdl/sdl_settings_interface.cpp index 90292a920..11841a275 100644 --- a/src/duckstation-sdl/sdl_settings_interface.cpp +++ b/src/duckstation-sdl/sdl_settings_interface.cpp @@ -20,6 +20,11 @@ SDLSettingsInterface::~SDLSettingsInterface() } } +void SDLSettingsInterface::Clear() +{ + m_ini.Reset(); +} + int SDLSettingsInterface::GetIntValue(const char* section, const char* key, int default_value /*= 0*/) { return static_cast(m_ini.GetLongValue(section, key, default_value)); diff --git a/src/duckstation-sdl/sdl_settings_interface.h b/src/duckstation-sdl/sdl_settings_interface.h index 3a51ebc55..d0c7d684c 100644 --- a/src/duckstation-sdl/sdl_settings_interface.h +++ b/src/duckstation-sdl/sdl_settings_interface.h @@ -9,6 +9,8 @@ public: SDLSettingsInterface(const char* filename); ~SDLSettingsInterface(); + void Clear() override; + int GetIntValue(const char* section, const char* key, int default_value = 0) override; float GetFloatValue(const char* section, const char* key, float default_value = 0.0f) override; bool GetBoolValue(const char* section, const char* key, bool default_value = false) override; diff --git a/src/frontend-common/CMakeLists.txt b/src/frontend-common/CMakeLists.txt index 6b4db6749..20e87e3dc 100644 --- a/src/frontend-common/CMakeLists.txt +++ b/src/frontend-common/CMakeLists.txt @@ -1,4 +1,6 @@ add_library(frontend-common + common_host_interface.cpp + common_host_interface.h icon.cpp icon.h imgui_styles.cpp diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp new file mode 100644 index 000000000..7668ea06b --- /dev/null +++ b/src/frontend-common/common_host_interface.cpp @@ -0,0 +1,374 @@ +#include "common_host_interface.h" +#include "common/assert.h" +#include "common/audio_stream.h" +#include "common/log.h" +#include "common/string_util.h" +#include "core/controller.h" +#include "core/game_list.h" +#include "core/gpu.h" +#include "core/system.h" +#include "sdl_audio_stream.h" +#include "sdl_controller_interface.h" +#include +Log_SetChannel(CommonHostInterface); + +CommonHostInterface::CommonHostInterface() : HostInterface() +{ + RegisterGeneralHotkeys(); + RegisterGraphicsHotkeys(); + RegisterSaveStateHotkeys(); +} + +CommonHostInterface::~CommonHostInterface() = default; + +void CommonHostInterface::SetFullscreen(bool enabled) {} + +void CommonHostInterface::ToggleFullscreen() {} + +std::unique_ptr CommonHostInterface::CreateAudioStream(AudioBackend backend) +{ + switch (backend) + { + case AudioBackend::Null: + return AudioStream::CreateNullAudioStream(); + + case AudioBackend::Cubeb: + return AudioStream::CreateCubebAudioStream(); + + case AudioBackend::SDL: + return SDLAudioStream::Create(); + + default: + return nullptr; + } +} + +void CommonHostInterface::SetDefaultSettings(SettingsInterface& si) +{ + HostInterface::SetDefaultSettings(si); + + si.SetStringValue("Controller1", "ButtonUp", "Keyboard/W"); + si.SetStringValue("Controller1", "ButtonDown", "Keyboard/S"); + si.SetStringValue("Controller1", "ButtonLeft", "Keyboard/A"); + si.SetStringValue("Controller1", "ButtonRight", "Keyboard/D"); + si.SetStringValue("Controller1", "ButtonSelect", "Keyboard/Backspace"); + si.SetStringValue("Controller1", "ButtonStart", "Keyboard/Return"); + si.SetStringValue("Controller1", "ButtonTriangle", "Keyboard/8"); + si.SetStringValue("Controller1", "ButtonCross", "Keyboard/2"); + si.SetStringValue("Controller1", "ButtonSquare", "Keyboard/4"); + si.SetStringValue("Controller1", "ButtonCircle", "Keyboard/6"); + si.SetStringValue("Controller1", "ButtonL1", "Keyboard/Q"); + si.SetStringValue("Controller1", "ButtonL2", "Keyboard/1"); + si.SetStringValue("Controller1", "ButtonR1", "Keyboard/E"); + si.SetStringValue("Controller1", "ButtonR2", "Keyboard/3"); + si.SetStringValue("Hotkeys", "FastForward", "Keyboard/Tab"); + si.SetStringValue("Hotkeys", "PowerOff", "Keyboard/Escape"); + si.SetStringValue("Hotkeys", "TogglePause", "Keyboard/Pause"); + si.SetStringValue("Hotkeys", "ToggleFullscreen", "Keyboard/Alt+Return"); +} + +#if 0 +void CommonHostInterface::refreshGameList(bool invalidate_cache /* = false */, bool invalidate_database /* = false */) +{ + std::lock_guard lock(m_qsettings_mutex); + QtSettingsInterface si(m_qsettings); + m_game_list->SetSearchDirectoriesFromSettings(si); + m_game_list->Refresh(invalidate_cache, invalidate_database); + emit gameListRefreshed(); +} +#endif + +std::optional +CommonHostInterface::GetHostKeyCode(const std::string_view key_code) const +{ + return std::nullopt; +} + +void CommonHostInterface::RegisterHotkey(String category, String name, String display_name, InputButtonHandler handler) +{ + m_hotkeys.push_back(HotkeyInfo{std::move(category), std::move(name), std::move(display_name), std::move(handler)}); +} + +bool CommonHostInterface::HandleHostKeyEvent(HostKeyCode key, bool pressed) +{ + const auto iter = m_keyboard_input_handlers.find(key); + if (iter == m_keyboard_input_handlers.end()) + return false; + + iter->second(pressed); + return true; +} + +void CommonHostInterface::UpdateInputMap(SettingsInterface& si) +{ + m_keyboard_input_handlers.clear(); + g_sdl_controller_interface.ClearControllerBindings(); + + UpdateControllerInputMap(si); + UpdateHotkeyInputMap(si); +} + +void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) +{ + 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 category = TinyString::FromFormat("Controller%u", controller_index + 1); + const auto button_names = Controller::GetButtonNames(ctype); + for (const auto& it : button_names) + { + const std::string& button_name = it.first; + const s32 button_code = it.second; + + const std::vector bindings = + si.GetStringList(category, TinyString::FromFormat("Button%s", button_name.c_str())); + for (const std::string& binding : bindings) + { + AddButtonToInputMap(binding, [this, controller_index, button_code](bool pressed) { + if (!m_system) + return; + + Controller* controller = m_system->GetController(controller_index); + if (controller) + controller->SetButtonState(button_code, pressed); + }); + } + } + + 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; + + const std::vector bindings = + si.GetStringList(category, TinyString::FromFormat("Axis%s", axis_name.c_str())); + for (const std::string& binding : bindings) + { + AddAxisToInputMap(binding, [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); + }); + } + } + } +} + +void CommonHostInterface::UpdateHotkeyInputMap(SettingsInterface& si) +{ + for (const HotkeyInfo& hi : m_hotkeys) + { + const std::vector bindings = si.GetStringList("Hotkeys", hi.name); + for (const std::string& binding : bindings) + AddButtonToInputMap(binding, hi.handler); + } +} + +void CommonHostInterface::AddButtonToInputMap(const std::string& binding, InputButtonHandler handler) +{ + const std::string::size_type slash_pos = binding.find('/'); + if (slash_pos == std::string::npos) + { + Log_WarningPrintf("Malformed button binding: '%s'", binding.c_str()); + return; + } + + const auto device = std::string_view(binding).substr(0, slash_pos); + const auto button = std::string_view(binding).substr(slash_pos + 1); + if (device == "Keyboard") + { + std::optional key_id = GetHostKeyCode(button); + if (!key_id.has_value()) + { + Log_WarningPrintf("Unknown keyboard key in binding '%s'", binding.c_str()); + return; + } + + m_keyboard_input_handlers.emplace(key_id.value(), std::move(handler)); + } + else if (device == "Controller") + { + const std::optional controller_index = StringUtil::FromChars(device.substr(10)); + if (!controller_index || *controller_index < 0) + { + Log_WarningPrintf("Invalid controller index in button binding '%s'", binding.c_str()); + return; + } + + if (button.find_first_of("Button") == 0) + { + const std::optional button_index = StringUtil::FromChars(button.substr(6)); + if (!button_index || + !g_sdl_controller_interface.BindControllerButton(*controller_index, *button_index, std::move(handler))) + { + Log_WarningPrintf("Failed to bind controller button '%s' to button", binding.c_str()); + return; + } + } + else if (button.find_first_of("+Axis") == 0 || button.find_first_of("-Axis")) + { + const std::optional axis_index = StringUtil::FromChars(button.substr(5)); + const bool positive = (button[0] == '+'); + if (!axis_index || !g_sdl_controller_interface.BindControllerAxisToButton(*controller_index, *axis_index, + positive, std::move(handler))) + { + Log_WarningPrintf("Failed to bind controller axis '%s' to button", binding.c_str()); + return; + } + } + else + { + Log_WarningPrintf("Malformed controller binding '%s' in button", binding.c_str()); + return; + } + } + else + { + Log_WarningPrintf("Unknown input device in button binding '%s'", binding.c_str()); + return; + } +} + +void CommonHostInterface::AddAxisToInputMap(const std::string& binding, InputAxisHandler handler) +{ + const std::string::size_type slash_pos = binding.find('/'); + if (slash_pos == std::string::npos) + { + Log_WarningPrintf("Malformed axis binding: '%s'", binding.c_str()); + return; + } + + const auto device = std::string_view(binding).substr(0, slash_pos); + const auto axis = std::string_view(binding).substr(slash_pos + 1); + if (device == "Controller") + { + const std::optional controller_index = StringUtil::FromChars(device.substr(10)); + if (!controller_index || *controller_index < 0) + { + Log_WarningPrintf("Invalid controller index in axis binding '%s'", binding.c_str()); + return; + } + + if (axis.find_first_of("Axis") == 0) + { + const std::optional axis_index = StringUtil::FromChars(axis.substr(4)); + if (!axis_index || + !g_sdl_controller_interface.BindControllerAxis(*controller_index, *axis_index, std::move(handler))) + { + Log_WarningPrintf("Failed to bind controller axis '%s' to axi", binding.c_str()); + return; + } + } + else + { + Log_WarningPrintf("Malformed controller binding '%s' in button", binding.c_str()); + return; + } + } + else + { + Log_WarningPrintf("Unknown input device in axis binding '%s'", binding.c_str()); + return; + } +} + +void CommonHostInterface::RegisterGeneralHotkeys() +{ + RegisterHotkey(StaticString("General"), StaticString("FastForward"), StaticString("Toggle Fast Forward"), + [this](bool pressed) { + m_speed_limiter_temp_disabled = pressed; + HostInterface::UpdateSpeedLimiterState(); + }); + + RegisterHotkey(StaticString("General"), StaticString("ToggleFullscreen"), StaticString("Toggle Fullscreen"), + [this](bool pressed) { + if (!pressed) + ToggleFullscreen(); + }); + + RegisterHotkey(StaticString("General"), StaticString("TogglePause"), StaticString("Toggle Pause"), + [this](bool pressed) { + if (!pressed) + PauseSystem(!m_paused); + }); + + RegisterHotkey(StaticString("General"), StaticString("PowerOff"), StaticString("Power Off System"), + [this](bool pressed) { + if (!pressed && m_system) + { + if (m_settings.confim_power_off) + { + SetFullscreen(false); + + SmallString confirmation_message("Are you sure you want to stop emulation?"); + if (m_settings.save_state_on_exit) + confirmation_message.AppendString("\n\nThe current state will be saved."); + + if (!ConfirmMessage(confirmation_message)) + { + if (m_settings.display_fullscreen) + SetFullscreen(true); + + m_system->ResetPerformanceCounters(); + return; + } + } + + PowerOffSystem(); + } + }); +} + +void CommonHostInterface::RegisterGraphicsHotkeys() +{ + RegisterHotkey(StaticString("Graphics"), StaticString("ToggleSoftwareRendering"), + StaticString("Toggle Software Rendering"), [this](bool pressed) { + if (!pressed) + ToggleSoftwareRendering(); + }); + + RegisterHotkey(StaticString("Graphics"), StaticString("IncreaseResolutionScale"), + StaticString("Increase Resolution Scale"), [this](bool pressed) { + if (!pressed) + ModifyResolutionScale(1); + }); + + RegisterHotkey(StaticString("Graphics"), StaticString("DecreaseResolutionScale"), + StaticString("Decrease Resolution Scale"), [this](bool pressed) { + if (!pressed) + ModifyResolutionScale(-1); + }); +} + +void CommonHostInterface::RegisterSaveStateHotkeys() +{ + for (u32 global_i = 0; global_i < 2; global_i++) + { + 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++) + { + RegisterHotkey(StaticString("Save States"), + TinyString::FromFormat("Load%sState%u", global ? "Global" : "Game", slot), + TinyString::FromFormat("Load %s State %u", global ? "Global" : "Game", slot), + [this, global, slot](bool pressed) { + if (!pressed) + LoadState(global, slot); + }); + RegisterHotkey(StaticString("Save States"), + TinyString::FromFormat("Save%sState%u", global ? "Global" : "Game", slot), + TinyString::FromFormat("Save %s State %u", global ? "Global" : "Game", slot), + [this, global, slot](bool pressed) { + if (!pressed) + SaveState(global, slot); + }); + } + } +} diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h new file mode 100644 index 000000000..e45fddc4f --- /dev/null +++ b/src/frontend-common/common_host_interface.h @@ -0,0 +1,65 @@ +#pragma once +#include "core/host_interface.h" +#include "common/string.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CommonHostInterface : public HostInterface +{ +public: + using HostKeyCode = s32; + + using InputButtonHandler = std::function; + using InputAxisHandler = std::function; + + struct HotkeyInfo + { + String category; + String name; + String display_name; + InputButtonHandler handler; + }; + + using HotkeyInfoList = std::vector; + + /// Returns a list of all available hotkeys. + const HotkeyInfoList& GetHotkeyInfoList() const { return m_hotkeys; } + +protected: + CommonHostInterface(); + ~CommonHostInterface(); + + virtual void SetFullscreen(bool enabled); + virtual void ToggleFullscreen(); + + virtual std::unique_ptr CreateAudioStream(AudioBackend backend) override; + + virtual void SetDefaultSettings(SettingsInterface& si) override; + + virtual std::optional GetHostKeyCode(const std::string_view key_code) const; + + void RegisterHotkey(String category, String name, String display_name, InputButtonHandler handler); + bool HandleHostKeyEvent(HostKeyCode code, bool pressed); + void UpdateInputMap(SettingsInterface& si); + +private: + void RegisterGeneralHotkeys(); + void RegisterGraphicsHotkeys(); + void RegisterSaveStateHotkeys(); + void UpdateControllerInputMap(SettingsInterface& si); + void UpdateHotkeyInputMap(SettingsInterface& si); + void AddButtonToInputMap(const std::string& binding, InputButtonHandler handler); + void AddAxisToInputMap(const std::string& binding, InputAxisHandler handler); + + HotkeyInfoList m_hotkeys; + + // input key maps + std::map m_keyboard_input_handlers; +}; diff --git a/src/frontend-common/frontend-common.vcxproj b/src/frontend-common/frontend-common.vcxproj index 62dad0cd0..6c909c739 100644 --- a/src/frontend-common/frontend-common.vcxproj +++ b/src/frontend-common/frontend-common.vcxproj @@ -46,6 +46,7 @@ + @@ -53,6 +54,7 @@ + diff --git a/src/frontend-common/frontend-common.vcxproj.filters b/src/frontend-common/frontend-common.vcxproj.filters index 32e24c37c..1ed7f95cf 100644 --- a/src/frontend-common/frontend-common.vcxproj.filters +++ b/src/frontend-common/frontend-common.vcxproj.filters @@ -6,6 +6,7 @@ + @@ -13,6 +14,7 @@ +