From 57d89e291de0eacfd368784309a0cbf89d38dcc8 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Wed, 23 Sep 2020 09:52:25 -0400 Subject: [PATCH] input_profiles: Implement input profiles --- src/yuzu/CMakeLists.txt | 2 + src/yuzu/configuration/config.cpp | 265 +++++++++++------- src/yuzu/configuration/config.h | 20 +- .../configure_debug_controller.cpp | 6 +- .../configure_debug_controller.h | 6 +- src/yuzu/configuration/configure_input.cpp | 30 +- src/yuzu/configuration/configure_input.h | 4 + .../configuration/configure_input_player.cpp | 111 +++++++- .../configuration/configure_input_player.h | 21 +- src/yuzu/configuration/configure_per_game.cpp | 3 +- src/yuzu/configuration/input_profiles.cpp | 131 +++++++++ src/yuzu/configuration/input_profiles.h | 32 +++ src/yuzu/main.cpp | 2 +- 13 files changed, 506 insertions(+), 127 deletions(-) create mode 100644 src/yuzu/configuration/input_profiles.cpp create mode 100644 src/yuzu/configuration/input_profiles.h diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 8abb74d56b..22fe0a2a6b 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -108,6 +108,8 @@ add_executable(yuzu configuration/configure_web.cpp configuration/configure_web.h configuration/configure_web.ui + configuration/input_profiles.cpp + configuration/input_profiles.h debugger/console.cpp debugger/console.h debugger/profiler.cpp diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 1ce62e4a62..5c8b02fbe2 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "common/common_paths.h" #include "common/file_util.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" @@ -14,14 +15,27 @@ namespace FS = Common::FS; -Config::Config(const std::string& config_file, bool is_global) { - // TODO: Don't hardcode the path; let the frontend decide where to put the config files. - qt_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + config_file; - FS::CreateFullPath(qt_config_loc); - qt_config = - std::make_unique(QString::fromStdString(qt_config_loc), QSettings::IniFormat); - global = is_global; - Reload(); +Config::Config(const std::string& config_file, ConfigType config_type) : type(config_type) { + global = config_type == ConfigType::GlobalConfig; + + switch (config_type) { + case ConfigType::GlobalConfig: + case ConfigType::PerGameConfig: + qt_config_loc = fmt::format("{}" DIR_SEP "{}.ini", FS::GetUserPath(FS::UserPath::ConfigDir), + config_file); + FS::CreateFullPath(qt_config_loc); + qt_config = std::make_unique(QString::fromStdString(qt_config_loc), + QSettings::IniFormat); + Reload(); + break; + case ConfigType::InputProfile: + qt_config_loc = fmt::format("{}input" DIR_SEP "{}.ini", + FS::GetUserPath(FS::UserPath::ConfigDir), config_file); + FS::CreateFullPath(qt_config_loc); + qt_config = std::make_unique(QString::fromStdString(qt_config_loc), + QSettings::IniFormat); + break; + } } Config::~Config() { @@ -242,84 +256,103 @@ const std::array Config::default_hotkeys{{ }}; // clang-format on -void Config::ReadPlayerValues() { - for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { - auto& player = Settings::values.players[p]; +void Config::ReadPlayerValue(std::size_t player_index) { + const QString player_prefix = [this, player_index] { + if (type == ConfigType::InputProfile) { + return QString{}; + } else { + return QStringLiteral("player_%1_").arg(player_index); + } + }(); + auto& player = Settings::values.players[player_index]; + + if (player_prefix.isEmpty()) { + const auto controller = static_cast( + qt_config + ->value(QStringLiteral("%1type").arg(player_prefix), + static_cast(Settings::ControllerType::ProController)) + .toUInt()); + + if (controller == Settings::ControllerType::LeftJoycon || + controller == Settings::ControllerType::RightJoycon) { + player.controller_type = controller; + } + } else { player.connected = - ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool(); + ReadSetting(QStringLiteral("%1connected").arg(player_prefix), false).toBool(); player.controller_type = static_cast( qt_config - ->value(QStringLiteral("player_%1_type").arg(p), + ->value(QStringLiteral("%1type").arg(player_prefix), static_cast(Settings::ControllerType::ProController)) .toUInt()); player.body_color_left = qt_config - ->value(QStringLiteral("player_%1_body_color_left").arg(p), + ->value(QStringLiteral("%1body_color_left").arg(player_prefix), Settings::JOYCON_BODY_NEON_BLUE) .toUInt(); - player.body_color_right = qt_config - ->value(QStringLiteral("player_%1_body_color_right").arg(p), - Settings::JOYCON_BODY_NEON_RED) - .toUInt(); - player.button_color_left = qt_config - ->value(QStringLiteral("player_%1_button_color_left").arg(p), - Settings::JOYCON_BUTTONS_NEON_BLUE) - .toUInt(); + player.body_color_right = + qt_config + ->value(QStringLiteral("%1body_color_right").arg(player_prefix), + Settings::JOYCON_BODY_NEON_RED) + .toUInt(); + player.button_color_left = + qt_config + ->value(QStringLiteral("%1button_color_left").arg(player_prefix), + Settings::JOYCON_BUTTONS_NEON_BLUE) + .toUInt(); player.button_color_right = qt_config - ->value(QStringLiteral("player_%1_button_color_right").arg(p), + ->value(QStringLiteral("%1button_color_right").arg(player_prefix), Settings::JOYCON_BUTTONS_NEON_RED) .toUInt(); + } - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_buttons[i]); - auto& player_buttons = player.buttons[i]; + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + auto& player_buttons = player.buttons[i]; - player_buttons = qt_config - ->value(QStringLiteral("player_%1_").arg(p) + - QString::fromUtf8(Settings::NativeButton::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (player_buttons.empty()) { - player_buttons = default_param; - } + player_buttons = qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeButton::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_buttons.empty()) { + player_buttons = default_param; } + } - for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_motions[i]); - auto& player_motions = player.motions[i]; + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + auto& player_motions = player.motions[i]; - player_motions = qt_config - ->value(QStringLiteral("player_%1_").arg(p) + - QString::fromUtf8(Settings::NativeMotion::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (player_motions.empty()) { - player_motions = default_param; - } + player_motions = qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeMotion::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_motions.empty()) { + player_motions = default_param; } + } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( - default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], - default_analogs[i][3], default_stick_mod[i], 0.5f); - auto& player_analogs = player.analogs[i]; + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + auto& player_analogs = player.analogs[i]; - player_analogs = qt_config - ->value(QStringLiteral("player_%1_").arg(p) + - QString::fromUtf8(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(default_param)) - .toString() - .toStdString(); - if (player_analogs.empty()) { - player_analogs = default_param; - } + player_analogs = qt_config + ->value(QStringLiteral("%1").arg(player_prefix) + + QString::fromUtf8(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (player_analogs.empty()) { + player_analogs = default_param; } } } @@ -436,7 +469,9 @@ void Config::ReadAudioValues() { void Config::ReadControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); - ReadPlayerValues(); + for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { + ReadPlayerValue(p); + } ReadDebugValues(); ReadKeyboardValues(); ReadMouseValues(); @@ -920,49 +955,55 @@ void Config::ReadValues() { ReadSystemValues(); } -void Config::SavePlayerValues() { - for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { - const auto& player = Settings::values.players[p]; +void Config::SavePlayerValue(std::size_t player_index) { + const QString player_prefix = [this, player_index] { + if (type == ConfigType::InputProfile) { + return QString{}; + } else { + return QStringLiteral("player_%1_").arg(player_index); + } + }(); - WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false); - WriteSetting(QStringLiteral("player_%1_type").arg(p), - static_cast(player.controller_type), - static_cast(Settings::ControllerType::ProController)); + const auto& player = Settings::values.players[player_index]; - WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left, + WriteSetting(QStringLiteral("%1type").arg(player_prefix), + static_cast(player.controller_type), + static_cast(Settings::ControllerType::ProController)); + + if (!player_prefix.isEmpty()) { + WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected, false); + WriteSetting(QStringLiteral("%1body_color_left").arg(player_prefix), player.body_color_left, Settings::JOYCON_BODY_NEON_BLUE); - WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right, - Settings::JOYCON_BODY_NEON_RED); - WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left, - Settings::JOYCON_BUTTONS_NEON_BLUE); - WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p), + WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix), + player.body_color_right, Settings::JOYCON_BODY_NEON_RED); + WriteSetting(QStringLiteral("%1button_color_left").arg(player_prefix), + player.button_color_left, Settings::JOYCON_BUTTONS_NEON_BLUE); + WriteSetting(QStringLiteral("%1button_color_right").arg(player_prefix), player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED); + } - for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_buttons[i]); - WriteSetting(QStringLiteral("player_%1_").arg(p) + - QString::fromStdString(Settings::NativeButton::mapping[i]), - QString::fromStdString(player.buttons[i]), - QString::fromStdString(default_param)); - } - for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { - const std::string default_param = - InputCommon::GenerateKeyboardParam(default_motions[i]); - WriteSetting(QStringLiteral("player_%1_").arg(p) + - QString::fromStdString(Settings::NativeMotion::mapping[i]), - QString::fromStdString(player.motions[i]), - QString::fromStdString(default_param)); - } - for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( - default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], - default_analogs[i][3], default_stick_mod[i], 0.5f); - WriteSetting(QStringLiteral("player_%1_").arg(p) + - QString::fromStdString(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(player.analogs[i]), - QString::fromStdString(default_param)); - } + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(player.buttons[i]), + QString::fromStdString(default_param)); + } + for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { + const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]); + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeMotion::mapping[i]), + QString::fromStdString(player.motions[i]), + QString::fromStdString(default_param)); + } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_stick_mod[i], 0.5f); + WriteSetting(QStringLiteral("%1").arg(player_prefix) + + QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(player.analogs[i]), + QString::fromStdString(default_param)); } } @@ -1087,7 +1128,9 @@ void Config::SaveAudioValues() { void Config::SaveControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); - SavePlayerValues(); + for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { + SavePlayerValue(p); + } SaveDebugValues(); SaveMouseValues(); SaveTouchscreenValues(); @@ -1515,3 +1558,19 @@ void Config::Save() { Settings::Sanitize(); SaveValues(); } + +void Config::ReadControlPlayerValue(std::size_t player_index) { + qt_config->beginGroup(QStringLiteral("Controls")); + ReadPlayerValue(player_index); + qt_config->endGroup(); +} + +void Config::SaveControlPlayerValue(std::size_t player_index) { + qt_config->beginGroup(QStringLiteral("Controls")); + SavePlayerValue(player_index); + qt_config->endGroup(); +} + +const std::string& Config::GetConfigFilePath() const { + return qt_config_loc; +} diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 5d8e45d782..a1ffca48fd 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -16,12 +16,24 @@ class QSettings; class Config { public: - explicit Config(const std::string& config_loc = "qt-config.ini", bool is_global = true); + enum class ConfigType { + GlobalConfig, + PerGameConfig, + InputProfile, + }; + + explicit Config(const std::string& config_loc = "qt-config", + ConfigType config_type = ConfigType::GlobalConfig); ~Config(); void Reload(); void Save(); + void ReadControlPlayerValue(std::size_t player_index); + void SaveControlPlayerValue(std::size_t player_index); + + const std::string& GetConfigFilePath() const; + static const std::array default_buttons; static const std::array default_motions; static const std::array, Settings::NativeAnalog::NumAnalogs> default_analogs; @@ -34,7 +46,7 @@ public: private: void ReadValues(); - void ReadPlayerValues(); + void ReadPlayerValue(std::size_t player_index); void ReadDebugValues(); void ReadKeyboardValues(); void ReadMouseValues(); @@ -62,7 +74,7 @@ private: void ReadWebServiceValues(); void SaveValues(); - void SavePlayerValues(); + void SavePlayerValue(std::size_t player_index); void SaveDebugValues(); void SaveMouseValues(); void SaveTouchscreenValues(); @@ -111,9 +123,9 @@ private: void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global, const QVariant& default_value); + ConfigType type; std::unique_ptr qt_config; std::string qt_config_loc; - bool global; }; diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp index 0097c9a295..6dc9c5e577 100644 --- a/src/yuzu/configuration/configure_debug_controller.cpp +++ b/src/yuzu/configuration/configure_debug_controller.cpp @@ -6,9 +6,11 @@ #include "yuzu/configuration/configure_debug_controller.h" ConfigureDebugController::ConfigureDebugController(QWidget* parent, - InputCommon::InputSubsystem* input_subsystem) + InputCommon::InputSubsystem* input_subsystem, + InputProfiles* profiles) : QDialog(parent), ui(std::make_unique()), - debug_controller(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, true)) { + debug_controller( + new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, true)) { ui->setupUi(this); ui->controllerLayout->addWidget(debug_controller); diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h index 34dcf705f2..2694b34194 100644 --- a/src/yuzu/configuration/configure_debug_controller.h +++ b/src/yuzu/configuration/configure_debug_controller.h @@ -10,6 +10,8 @@ class QPushButton; +class InputProfiles; + namespace InputCommon { class InputSubsystem; } @@ -22,8 +24,8 @@ class ConfigureDebugController : public QDialog { Q_OBJECT public: - explicit ConfigureDebugController(QWidget* parent, - InputCommon::InputSubsystem* input_subsystem); + explicit ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem, + InputProfiles* profiles); ~ConfigureDebugController() override; void ApplyConfiguration(); diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index f2932aa0b2..523ece4266 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -23,6 +23,7 @@ #include "yuzu/configuration/configure_motion_touch.h" #include "yuzu/configuration/configure_mouse_advanced.h" #include "yuzu/configuration/configure_touchscreen_advanced.h" +#include "yuzu/configuration/input_profiles.h" namespace { template @@ -64,7 +65,8 @@ void OnDockedModeChanged(bool last_state, bool new_state) { } ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique()) { + : QWidget(parent), ui(std::make_unique()), + profiles(std::make_unique()) { ui->setupUi(this); } @@ -73,14 +75,22 @@ ConfigureInput::~ConfigureInput() = default; void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, std::size_t max_players) { player_controllers = { - new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem), - new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem), + new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem, + profiles.get()), + new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem, + profiles.get()), }; player_tabs = { @@ -134,7 +144,7 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced)); ui->tabAdvanced->layout()->addWidget(advanced); connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] { - CallConfigureDialog(*this, input_subsystem); + CallConfigureDialog(*this, input_subsystem, profiles.get()); }); connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] { CallConfigureDialog(*this, input_subsystem); diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 0e8b2fd4ee..f135a4299a 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -19,6 +19,8 @@ class QCheckBox; class QString; class QTimer; +class InputProfiles; + namespace InputCommon { class InputSubsystem; } @@ -61,6 +63,8 @@ private: std::unique_ptr ui; + std::unique_ptr profiles; + std::array player_controllers; std::array player_tabs; std::array player_connected; diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 0de0c69997..b4de2f6afd 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -22,6 +22,8 @@ #include "ui_configure_input_player.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/input_profiles.h" +#include "yuzu/util/limitable_input_dialog.h" constexpr std::size_t HANDHELD_INDEX = 8; @@ -240,10 +242,11 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, InputCommon::InputSubsystem* input_subsystem_, - bool debug) + InputProfiles* profiles_, bool debug) : QWidget(parent), ui(std::make_unique()), player_index(player_index), - debug(debug), input_subsystem{input_subsystem_}, timeout_timer(std::make_unique()), - poll_timer(std::make_unique()), bottom_row(bottom_row) { + debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_), + timeout_timer(std::make_unique()), poll_timer(std::make_unique()), + bottom_row(bottom_row) { ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -521,6 +524,17 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } }); + RefreshInputProfiles(); + + connect(ui->buttonProfilesNew, &QPushButton::clicked, this, + &ConfigureInputPlayer::CreateProfile); + connect(ui->buttonProfilesDelete, &QPushButton::clicked, this, + &ConfigureInputPlayer::DeleteProfile); + connect(ui->comboProfiles, qOverload(&QComboBox::activated), this, + &ConfigureInputPlayer::LoadProfile); + connect(ui->buttonProfilesSave, &QPushButton::clicked, this, + &ConfigureInputPlayer::SaveProfile); + LoadConfiguration(); // TODO(wwylele): enable this when we actually emulate it @@ -1061,3 +1075,94 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { SetPollingResult({}, true); } + +void ConfigureInputPlayer::CreateProfile() { + const auto profile_name = + LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20); + + if (profile_name.isEmpty()) { + return; + } + + if (!profiles->IsProfileNameValid(profile_name.toStdString())) { + QMessageBox::critical(this, tr("Create Input Profile"), + tr("The given profile name is not valid!")); + return; + } + + ApplyConfiguration(); + + if (!profiles->CreateProfile(profile_name.toStdString(), player_index)) { + QMessageBox::critical(this, tr("Create Input Profile"), + tr("Failed to create the input profile \"%1\"").arg(profile_name)); + RefreshInputProfiles(); + return; + } + + ui->comboProfiles->addItem(profile_name); + ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1); +} + +void ConfigureInputPlayer::DeleteProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { + return; + } + + if (!profiles->DeleteProfile(profile_name.toStdString())) { + QMessageBox::critical(this, tr("Delete Input Profile"), + tr("Failed to delete the input profile \"%1\"").arg(profile_name)); + RefreshInputProfiles(); + return; + } + + ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex()); + ui->comboProfiles->setCurrentIndex(-1); +} + +void ConfigureInputPlayer::LoadProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { + return; + } + + ApplyConfiguration(); + + if (!profiles->LoadProfile(profile_name.toStdString(), player_index)) { + QMessageBox::critical(this, tr("Load Input Profile"), + tr("Failed to load the input profile \"%1\"").arg(profile_name)); + RefreshInputProfiles(); + return; + } + + LoadConfiguration(); +} + +void ConfigureInputPlayer::SaveProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { + return; + } + + ApplyConfiguration(); + + if (!profiles->SaveProfile(profile_name.toStdString(), player_index)) { + QMessageBox::critical(this, tr("Save Input Profile"), + tr("Failed to save the input profile \"%1\"").arg(profile_name)); + RefreshInputProfiles(); + return; + } +} + +void ConfigureInputPlayer::RefreshInputProfiles() { + ui->comboProfiles->clear(); + + for (const auto& profile_name : profiles->GetInputProfileNames()) { + ui->comboProfiles->addItem(QString::fromStdString(profile_name)); + } + + ui->comboProfiles->setCurrentIndex(-1); +} diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index a5414e624c..05dee5af51 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -26,6 +26,8 @@ class QString; class QTimer; class QWidget; +class InputProfiles; + namespace InputCommon { class InputSubsystem; } @@ -45,7 +47,7 @@ class ConfigureInputPlayer : public QWidget { public: explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, InputCommon::InputSubsystem* input_subsystem_, - bool debug = false); + InputProfiles* profiles_, bool debug = false); ~ConfigureInputPlayer() override; /// Save all button configurations to settings file. @@ -116,6 +118,21 @@ private: /// Gets the default controller mapping for this device and auto configures the input to match. void UpdateMappingWithDefaults(); + /// Creates a controller profile. + void CreateProfile(); + + /// Deletes the selected controller profile. + void DeleteProfile(); + + /// Loads the selected controller profile. + void LoadProfile(); + + /// Saves the current controller configuration into a selected controller profile. + void SaveProfile(); + + /// Refreshes the list of controller profiles. + void RefreshInputProfiles(); + std::unique_ptr ui; std::size_t player_index; @@ -123,6 +140,8 @@ private: InputCommon::InputSubsystem* input_subsystem; + InputProfiles* profiles; + std::unique_ptr timeout_timer; std::unique_ptr poll_timer; diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 002db3f930..81464dd371 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -29,7 +29,8 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id) : QDialog(parent), ui(std::make_unique()), title_id(title_id) { - game_config = std::make_unique(fmt::format("{:016X}.ini", title_id), false); + game_config = std::make_unique(fmt::format("{:016X}", title_id), + Config::ConfigType::PerGameConfig); Settings::SetConfiguringGlobal(false); diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp new file mode 100644 index 0000000000..e87aededb3 --- /dev/null +++ b/src/yuzu/configuration/input_profiles.cpp @@ -0,0 +1,131 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/common_paths.h" +#include "common/file_util.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/input_profiles.h" + +namespace FS = Common::FS; + +namespace { + +bool ProfileExistsInFilesystem(std::string_view profile_name) { + return FS::Exists(fmt::format("{}input" DIR_SEP "{}.ini", + FS::GetUserPath(FS::UserPath::ConfigDir), profile_name)); +} + +bool IsINI(std::string_view filename) { + const std::size_t index = filename.rfind('.'); + + if (index == std::string::npos) { + return false; + } + + return filename.substr(index) == ".ini"; +} + +std::string GetNameWithoutExtension(const std::string& filename) { + const std::size_t index = filename.rfind('.'); + + if (index == std::string::npos) { + return filename; + } + + return filename.substr(0, index); +} + +} // namespace + +InputProfiles::InputProfiles() { + const std::string input_profile_loc = + fmt::format("{}input", FS::GetUserPath(FS::UserPath::ConfigDir)); + + FS::ForeachDirectoryEntry( + nullptr, input_profile_loc, + [this](u64* entries_out, const std::string& directory, const std::string& filename) { + if (IsINI(filename) && IsProfileNameValid(GetNameWithoutExtension(filename))) { + map_profiles.insert_or_assign( + GetNameWithoutExtension(filename), + std::make_unique(GetNameWithoutExtension(filename), + Config::ConfigType::InputProfile)); + } + return true; + }); +} + +InputProfiles::~InputProfiles() = default; + +std::vector InputProfiles::GetInputProfileNames() { + std::vector profile_names; + profile_names.reserve(map_profiles.size()); + + for (const auto& [profile_name, config] : map_profiles) { + if (!ProfileExistsInFilesystem(profile_name)) { + DeleteProfile(profile_name); + continue; + } + + profile_names.push_back(profile_name); + } + + return profile_names; +} + +bool InputProfiles::IsProfileNameValid(std::string_view profile_name) { + return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos; +} + +bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t player_index) { + if (ProfileExistsInMap(profile_name)) { + return false; + } + + map_profiles.insert_or_assign( + profile_name, std::make_unique(profile_name, Config::ConfigType::InputProfile)); + + return SaveProfile(profile_name, player_index); +} + +bool InputProfiles::DeleteProfile(const std::string& profile_name) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + if (!ProfileExistsInFilesystem(profile_name) || + FS::Delete(map_profiles[profile_name]->GetConfigFilePath())) { + map_profiles.erase(profile_name); + } + + return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name); +} + +bool InputProfiles::LoadProfile(const std::string& profile_name, std::size_t player_index) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + if (!ProfileExistsInFilesystem(profile_name)) { + map_profiles.erase(profile_name); + return false; + } + + map_profiles[profile_name]->ReadControlPlayerValue(player_index); + return true; +} + +bool InputProfiles::SaveProfile(const std::string& profile_name, std::size_t player_index) { + if (!ProfileExistsInMap(profile_name)) { + return false; + } + + map_profiles[profile_name]->SaveControlPlayerValue(player_index); + return true; +} + +bool InputProfiles::ProfileExistsInMap(const std::string& profile_name) const { + return map_profiles.find(profile_name) != map_profiles.end(); +} diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h new file mode 100644 index 0000000000..cb41fd9bee --- /dev/null +++ b/src/yuzu/configuration/input_profiles.h @@ -0,0 +1,32 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +class Config; + +class InputProfiles { + +public: + explicit InputProfiles(); + virtual ~InputProfiles(); + + std::vector GetInputProfileNames(); + + static bool IsProfileNameValid(std::string_view profile_name); + + bool CreateProfile(const std::string& profile_name, std::size_t player_index); + bool DeleteProfile(const std::string& profile_name); + bool LoadProfile(const std::string& profile_name, std::size_t player_index); + bool SaveProfile(const std::string& profile_name, std::size_t player_index); + +private: + bool ProfileExistsInMap(const std::string& profile_name) const; + + std::unordered_map> map_profiles; +}; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 18e68e5907..4ff7fd92ff 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1087,7 +1087,7 @@ void GMainWindow::BootGame(const QString& filename) { const auto loader = Loader::GetLoader(v_file); if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) { // Load per game settings - Config per_game_config(fmt::format("{:016X}.ini", title_id), false); + Config per_game_config(fmt::format("{:016X}", title_id), Config::ConfigType::PerGameConfig); } Settings::LogSettings();