From 4dc9e10777072998f12b49847ce660817668fa49 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Wed, 1 Jul 2020 00:33:45 +1000 Subject: [PATCH] HostInterface: Support per-controller-type settings --- src/core/analog_controller.cpp | 19 +++++++ src/core/analog_controller.h | 5 ++ src/core/controller.cpp | 16 +++++- src/core/controller.h | 9 +++ src/core/host_interface.cpp | 33 +++++++++++ src/core/host_interface.h | 18 +++++- src/core/settings.cpp | 56 +++++++++++++++++++ src/core/settings.h | 32 +++++++++++ src/core/system.cpp | 13 +++++ src/core/system.h | 1 + .../libretro_host_interface.cpp | 12 ++++ .../libretro_host_interface.h | 1 + src/duckstation-qt/qthostinterface.cpp | 7 +++ src/duckstation-qt/qthostinterface.h | 1 + src/duckstation-sdl/sdl_host_interface.cpp | 5 ++ src/duckstation-sdl/sdl_host_interface.h | 2 + src/frontend-common/common_host_interface.cpp | 24 ++++++-- 17 files changed, 247 insertions(+), 7 deletions(-) diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp index 93d0b66cf..65d7af65e 100644 --- a/src/core/analog_controller.cpp +++ b/src/core/analog_controller.cpp @@ -1,6 +1,7 @@ #include "analog_controller.h" #include "common/log.h" #include "common/state_wrapper.h" +#include "common/string_util.h" #include "host_interface.h" #include "system.h" Log_SetChannel(AnalogController); @@ -23,6 +24,9 @@ void AnalogController::Reset() m_rumble_unlocked = false; m_configuration_mode = false; m_command_param = 0; + + if (m_auto_enable_analog) + SetAnalogMode(true); } bool AnalogController::DoState(StateWrapper& sw) @@ -474,3 +478,18 @@ u32 AnalogController::StaticGetVibrationMotorCount() { return NUM_MOTORS; } + +Controller::SettingList AnalogController::StaticGetSettings() +{ + static constexpr std::array settings = { + {{SettingInfo::Type::Boolean, "AutoEnableAnalog", "Enable Analog Mode on Reset", + "Automatically enables analog mode when the console is reset/powered on.", "false"}}}; + + return SettingList(settings.begin(), settings.end()); +} + +void AnalogController::LoadSettings(HostInterface* host_interface, const char* section) +{ + Controller::LoadSettings(host_interface, section); + m_auto_enable_analog = host_interface->GetBooleanSettingValue(section, "AutoEnableAnalog", false); +} diff --git a/src/core/analog_controller.h b/src/core/analog_controller.h index d759aec20..8679323e6 100644 --- a/src/core/analog_controller.h +++ b/src/core/analog_controller.h @@ -50,6 +50,7 @@ public: static AxisList StaticGetAxisNames(); static ButtonList StaticGetButtonNames(); static u32 StaticGetVibrationMotorCount(); + static SettingList StaticGetSettings(); ControllerType GetType() const override; std::optional GetAxisCodeByName(std::string_view axis_name) const override; @@ -70,6 +71,8 @@ public: u32 GetVibrationMotorCount() const override; float GetVibrationMotorStrength(u32 motor) override; + void LoadSettings(HostInterface* host_interface, const char* section) override; + private: using MotorState = std::array; @@ -132,6 +135,8 @@ private: System* m_system; u32 m_index; + bool m_auto_enable_analog = false; + bool m_analog_mode = false; bool m_analog_locked = false; bool m_rumble_unlocked = false; diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 9c38cc864..53d7fd22a 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -3,8 +3,8 @@ #include "common/state_wrapper.h" #include "digital_controller.h" #include "namco_guncon.h" -#include "playstation_mouse.h" #include "negcon.h" +#include "playstation_mouse.h" Controller::Controller() = default; @@ -39,6 +39,8 @@ float Controller::GetVibrationMotorStrength(u32 motor) return 0.0f; } +void Controller::LoadSettings(HostInterface* host_interface, const char* section) {} + std::unique_ptr Controller::Create(System* system, ControllerType type, u32 index) { switch (type) @@ -198,3 +200,15 @@ std::optional Controller::GetButtonCodeByName(ControllerType type, std::str return std::nullopt; } } + +Controller::SettingList Controller::GetSettings(ControllerType type) +{ + switch (type) + { + case ControllerType::AnalogController: + return AnalogController::StaticGetSettings(); + + default: + return {}; + } +} diff --git a/src/core/controller.h b/src/core/controller.h index 908a295e7..1f9ca51b8 100644 --- a/src/core/controller.h +++ b/src/core/controller.h @@ -1,4 +1,5 @@ #pragma once +#include "settings.h" #include "types.h" #include #include @@ -8,12 +9,14 @@ class StateWrapper; class System; +class HostInterface; class Controller { public: using ButtonList = std::vector>; using AxisList = std::vector>; + using SettingList = std::vector; Controller(); virtual ~Controller(); @@ -48,6 +51,9 @@ public: /// Queries the state of the specified vibration motor. Values are normalized from 0..1. virtual float GetVibrationMotorStrength(u32 motor); + /// Loads/refreshes any per-controller settings. + virtual void LoadSettings(HostInterface* host_interface, const char* section); + /// Creates a new controller of the specified type. static std::unique_ptr Create(System* system, ControllerType type, u32 index); @@ -65,4 +71,7 @@ public: /// Returns the number of vibration motors. static u32 GetVibrationMotorCount(ControllerType type); + + /// Returns settings for the controller. + static SettingList GetSettings(ControllerType type); }; diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 28edec025..57343bf07 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -480,6 +480,9 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) OnControllerTypeChanged(i); } + + if (m_system && !controllers_updated) + m_system->UpdateControllerSettings(); } if (m_display && m_settings.display_linear_filtering != old_settings.display_linear_filtering) @@ -566,6 +569,36 @@ std::string HostInterface::GetGameMemoryCardPath(const char* game_code, u32 slot return GetUserDirectoryRelativePath("memcards/%s_%d.mcd", game_code, slot + 1); } +bool HostInterface::GetBooleanSettingValue(const char* section, const char* key, bool default_value /*= false*/) +{ + std::string value = GetSettingValue(section, key, ""); + if (value.empty()) + return default_value; + + std::optional bool_value = StringUtil::FromChars(value); + return bool_value.value_or(default_value); +} + +bool HostInterface::GetIntegerSettingValue(const char* section, const char* key, s32 default_value /*= 0*/) +{ + std::string value = GetSettingValue(section, key, ""); + if (value.empty()) + return default_value; + + std::optional int_value = StringUtil::FromChars(value); + return int_value.value_or(default_value); +} + +bool HostInterface::GetFloatSettingValue(const char* section, const char* key, float default_value /*= 0.0f*/) +{ + std::string value = GetSettingValue(section, key, ""); + if (value.empty()) + return default_value; + + std::optional float_value = StringUtil::FromChars(value); + return float_value.value_or(default_value); +} + void HostInterface::ToggleSoftwareRendering() { if (!m_system || m_settings.gpu_renderer == GPURenderer::Software) diff --git a/src/core/host_interface.h b/src/core/host_interface.h index ac328d0e2..42194dca2 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -91,7 +91,8 @@ public: /// Displays a loading screen with the logo, rendered with ImGui. Use when executing possibly-time-consuming tasks /// such as compiling shaders when starting up. - virtual void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, int progress_value = -1); + virtual void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, + int progress_value = -1); /// Retrieves information about specified game from game list. virtual void GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title); @@ -102,7 +103,20 @@ public: /// Returns the default path to a memory card for a specific game. virtual std::string GetGameMemoryCardPath(const char* game_code, u32 slot) const; - /// Enables the software cursor. Can be called multiple times, but must be matched by a call to DisableSoftwareCursor(). + /// Returns a setting value from the configuration. + virtual std::string GetSettingValue(const char* section, const char* key, const char* default_value = "") = 0; + + /// Returns a boolean setting from the configuration. + bool GetBooleanSettingValue(const char* section, const char* key, bool default_value = false); + + /// Returns an integer setting from the configuration. + bool GetIntegerSettingValue(const char* section, const char* key, s32 default_value = 0); + + /// Returns a float setting from the configuration. + bool GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f); + + /// Enables the software cursor. Can be called multiple times, but must be matched by a call to + /// DisableSoftwareCursor(). void EnableSoftwareCursor(); /// Disables the software cursor, preventing it from being renderered. diff --git a/src/core/settings.cpp b/src/core/settings.cpp index f12f3aebe..14002dce7 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -4,6 +4,62 @@ #include #include +const char* SettingInfo::StringDefaultValue() const +{ + return default_value ? default_value : ""; +} + +bool SettingInfo::BooleanDefaultValue() const +{ + return default_value ? StringUtil::FromChars(default_value).value_or(false) : false; +} + +s32 SettingInfo::IntegerDefaultValue() const +{ + return default_value ? StringUtil::FromChars(default_value).value_or(0) : 0; +} + +s32 SettingInfo::IntegerMinValue() const +{ + static constexpr s32 fallback_value = std::numeric_limits::min(); + return min_value ? StringUtil::FromChars(min_value).value_or(fallback_value) : fallback_value; +} + +s32 SettingInfo::IntegerMaxValue() const +{ + static constexpr s32 fallback_value = std::numeric_limits::max(); + return max_value ? StringUtil::FromChars(max_value).value_or(fallback_value) : fallback_value; +} + +s32 SettingInfo::IntegerStepValue() const +{ + static constexpr s32 fallback_value = 1; + return step_value ? StringUtil::FromChars(step_value).value_or(fallback_value) : fallback_value; +} + +float SettingInfo::FloatDefaultValue() const +{ + return default_value ? StringUtil::FromChars(default_value).value_or(0.0f) : 0.0f; +} + +float SettingInfo::FloatMinValue() const +{ + static constexpr float fallback_value = std::numeric_limits::min(); + return min_value ? StringUtil::FromChars(min_value).value_or(fallback_value) : fallback_value; +} + +float SettingInfo::FloatMaxValue() const +{ + static constexpr float fallback_value = std::numeric_limits::max(); + return max_value ? StringUtil::FromChars(max_value).value_or(fallback_value) : fallback_value; +} + +float SettingInfo::FloatStepValue() const +{ + static constexpr float fallback_value = 0.1f; + return step_value ? StringUtil::FromChars(step_value).value_or(fallback_value) : fallback_value; +} + Settings::Settings() = default; bool Settings::HasAnyPerGameMemoryCards() const diff --git a/src/core/settings.h b/src/core/settings.h index 6821d18a8..8337581c4 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -30,6 +30,38 @@ public: virtual void DeleteValue(const char* section, const char* key) = 0; }; +struct SettingInfo +{ + enum class Type + { + Boolean, + Integer, + Float, + String, + Path, + }; + + Type type; + const char* key; + const char* visible_name; + const char* description; + const char* default_value; + const char* min_value; + const char* max_value; + const char* step_value; + + const char* StringDefaultValue() const; + bool BooleanDefaultValue() const; + s32 IntegerDefaultValue() const; + s32 IntegerMinValue() const; + s32 IntegerMaxValue() const; + s32 IntegerStepValue() const; + float FloatDefaultValue() const; + float FloatMinValue() const; + float FloatMaxValue() const; + float FloatStepValue() const; +}; + struct Settings { Settings(); diff --git a/src/core/system.cpp b/src/core/system.cpp index 4c190a644..ceb997fa3 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -832,11 +832,24 @@ void System::UpdateControllers() { std::unique_ptr controller = Controller::Create(this, type, i); if (controller) + { + controller->LoadSettings(m_host_interface, TinyString::FromFormat("Controller%u", i + 1u)); m_pad->SetController(i, std::move(controller)); + } } } } +void System::UpdateControllerSettings() +{ + for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) + { + Controller* controller = m_pad->GetController(i); + if (controller) + controller->LoadSettings(m_host_interface, TinyString::FromFormat("Controller%u", i + 1u)); + } +} + void System::UpdateMemoryCards() { const Settings& settings = m_host_interface->GetSettings(); diff --git a/src/core/system.h b/src/core/system.h index e9865f9eb..a8cfb5349 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -139,6 +139,7 @@ public: // Access controllers for simulating input. Controller* GetController(u32 slot) const; void UpdateControllers(); + void UpdateControllerSettings(); void UpdateMemoryCards(); bool HasMedia() const; diff --git a/src/duckstation-libretro/libretro_host_interface.cpp b/src/duckstation-libretro/libretro_host_interface.cpp index 7ee605f46..a1c98e4c5 100644 --- a/src/duckstation-libretro/libretro_host_interface.cpp +++ b/src/duckstation-libretro/libretro_host_interface.cpp @@ -118,6 +118,18 @@ std::string LibretroHostInterface::GetGameMemoryCardPath(const char* game_code, return GetUserDirectoryRelativePath("%s/%s_%d.mcd", GetSaveDirectory(), game_code, slot + 1); } +std::string LibretroHostInterface::GetSettingValue(const char* section, const char* key, + const char* default_value /*= ""*/) +{ + TinyString name; + name.Format("%s.%s", section, key); + retro_variable var{name, default_value}; + if (g_retro_environment_callback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + return var.value; + else + return default_value; +} + void LibretroHostInterface::AddOSDMessage(std::string message, float duration /*= 2.0f*/) { retro_message msg = {}; diff --git a/src/duckstation-libretro/libretro_host_interface.h b/src/duckstation-libretro/libretro_host_interface.h index 11ecea47a..14bf734ea 100644 --- a/src/duckstation-libretro/libretro_host_interface.h +++ b/src/duckstation-libretro/libretro_host_interface.h @@ -24,6 +24,7 @@ public: void GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title) override; std::string GetSharedMemoryCardPath(u32 slot) const override; std::string GetGameMemoryCardPath(const char* game_code, u32 slot) const override; + std::string GetSettingValue(const char* section, const char* key, const char* default_value = "") override; // Called by frontend void retro_get_system_av_info(struct retro_system_av_info* info); diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index d4944a5b2..619e82ba6 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -122,6 +122,13 @@ bool QtHostInterface::parseCommandLineParameters(int argc, char* argv[], return CommonHostInterface::ParseCommandLineParameters(argc, argv, out_boot_params); } +std::string QtHostInterface::GetSettingValue(const char* section, const char* key, const char* default_value) +{ + std::lock_guard guard(m_qsettings_mutex); + QVariant value = m_qsettings->value(QStringLiteral("%1/%2").arg(section).arg(key), QString(default_value)); + return value.toString().toStdString(); +} + QVariant QtHostInterface::getSettingValue(const QString& name, const QVariant& default_value) { std::lock_guard guard(m_qsettings_mutex); diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 187e057e6..6ebfc9eb7 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -50,6 +50,7 @@ public: bool parseCommandLineParameters(int argc, char* argv[], std::unique_ptr* out_boot_params); /// Thread-safe QSettings access. + std::string GetSettingValue(const char* section, const char* key, const char* default_value = "") override; QVariant getSettingValue(const QString& name, const QVariant& default_value = QVariant()); void putSettingValue(const QString& name, const QVariant& value); void removeSettingValue(const QString& name); diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 610a5c24a..2c7cd8728 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -456,6 +456,11 @@ void SDLHostInterface::Shutdown() CommonHostInterface::Shutdown(); } +std::string SDLHostInterface::GetSettingValue(const char* section, const char* key, const char* default_value /*= ""*/) +{ + return m_settings_interface->GetStringValue(section, key, default_value); +} + void SDLHostInterface::LoadSettings() { // Settings need to be loaded prior to creating the window for OpenGL bits. diff --git a/src/duckstation-sdl/sdl_host_interface.h b/src/duckstation-sdl/sdl_host_interface.h index ad019fbcd..be42d6991 100644 --- a/src/duckstation-sdl/sdl_host_interface.h +++ b/src/duckstation-sdl/sdl_host_interface.h @@ -34,6 +34,8 @@ public: bool Initialize() override; void Shutdown() override; + std::string GetSettingValue(const char* section, const char* key, const char* default_value = "") override; + void Run(); protected: diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 5dcdff0f9..b0e711b3b 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -1483,13 +1483,21 @@ void CommonHostInterface::ApplyInputProfile(const char* profile_path, SettingsIn const std::string rumble_value = profile.GetStringValue(section_name, "Rumble"); if (!rumble_value.empty()) si.SetStringValue(section_name, "Rumble", rumble_value.c_str()); - } - UpdateInputMap(si); + Controller::SettingList settings = Controller::GetSettings(*ctype); + for (const SettingInfo& ssi : settings) + { + const std::string value = profile.GetStringValue(section_name, ssi.key, ""); + if (!value.empty()) + si.SetStringValue(section_name, ssi.key, value.c_str()); + } + } if (m_system) m_system->UpdateControllers(); + UpdateInputMap(si); + ReportFormattedMessage("Loaded input profile from '%s'", profile_path); } @@ -1532,6 +1540,14 @@ bool CommonHostInterface::SaveInputProfile(const char* profile_path, SettingsInt const std::string rumble_value = si.GetStringValue(section_name, "Rumble"); if (!rumble_value.empty()) profile.SetStringValue(section_name, "Rumble", rumble_value.c_str()); + + Controller::SettingList settings = Controller::GetSettings(ctype); + for (const SettingInfo& ssi : settings) + { + const std::string value = si.GetStringValue(section_name, ssi.key, ""); + if (!value.empty()) + profile.SetStringValue(section_name, ssi.key, value.c_str()); + } } if (!profile.Save()) @@ -1814,8 +1830,8 @@ void CommonHostInterface::DisplayLoadingScreen(const char* message, int progress const bool has_progress = (progress_min < progress_max); // eat the last imgui frame, it might've been partially rendered by the caller. - //ImGui::EndFrame(); - //ImGui::NewFrame(); + // ImGui::EndFrame(); + // ImGui::NewFrame(); ImGui::SetNextWindowSize(ImVec2(width, (has_progress ? 50.0f : 30.0f) * scale), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), ImGuiCond_Always,