input_profiles: Implement input profiles

This commit is contained in:
Morph 2020-09-23 09:52:25 -04:00
parent 75eaab2e0f
commit 57d89e291d
13 changed files with 506 additions and 127 deletions

View File

@ -108,6 +108,8 @@ add_executable(yuzu
configuration/configure_web.cpp configuration/configure_web.cpp
configuration/configure_web.h configuration/configure_web.h
configuration/configure_web.ui configuration/configure_web.ui
configuration/input_profiles.cpp
configuration/input_profiles.h
debugger/console.cpp debugger/console.cpp
debugger/console.h debugger/console.h
debugger/profiler.cpp debugger/profiler.cpp

View File

@ -5,6 +5,7 @@
#include <array> #include <array>
#include <QKeySequence> #include <QKeySequence>
#include <QSettings> #include <QSettings>
#include "common/common_paths.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h" #include "core/hle/service/hid/controllers/npad.h"
@ -14,14 +15,27 @@
namespace FS = Common::FS; namespace FS = Common::FS;
Config::Config(const std::string& config_file, bool is_global) { Config::Config(const std::string& config_file, ConfigType config_type) : type(config_type) {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files. global = config_type == ConfigType::GlobalConfig;
qt_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + config_file;
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); FS::CreateFullPath(qt_config_loc);
qt_config = qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); QSettings::IniFormat);
global = is_global;
Reload(); 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<QSettings>(QString::fromStdString(qt_config_loc),
QSettings::IniFormat);
break;
}
} }
Config::~Config() { Config::~Config() {
@ -242,44 +256,65 @@ const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
}}; }};
// clang-format on // clang-format on
void Config::ReadPlayerValues() { void Config::ReadPlayerValue(std::size_t player_index) {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { const QString player_prefix = [this, player_index] {
auto& player = Settings::values.players[p]; 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<Settings::ControllerType>(
qt_config
->value(QStringLiteral("%1type").arg(player_prefix),
static_cast<u8>(Settings::ControllerType::ProController))
.toUInt());
if (controller == Settings::ControllerType::LeftJoycon ||
controller == Settings::ControllerType::RightJoycon) {
player.controller_type = controller;
}
} else {
player.connected = player.connected =
ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool(); ReadSetting(QStringLiteral("%1connected").arg(player_prefix), false).toBool();
player.controller_type = static_cast<Settings::ControllerType>( player.controller_type = static_cast<Settings::ControllerType>(
qt_config qt_config
->value(QStringLiteral("player_%1_type").arg(p), ->value(QStringLiteral("%1type").arg(player_prefix),
static_cast<u8>(Settings::ControllerType::ProController)) static_cast<u8>(Settings::ControllerType::ProController))
.toUInt()); .toUInt());
player.body_color_left = qt_config 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) Settings::JOYCON_BODY_NEON_BLUE)
.toUInt(); .toUInt();
player.body_color_right = qt_config player.body_color_right =
->value(QStringLiteral("player_%1_body_color_right").arg(p), qt_config
->value(QStringLiteral("%1body_color_right").arg(player_prefix),
Settings::JOYCON_BODY_NEON_RED) Settings::JOYCON_BODY_NEON_RED)
.toUInt(); .toUInt();
player.button_color_left = qt_config player.button_color_left =
->value(QStringLiteral("player_%1_button_color_left").arg(p), qt_config
->value(QStringLiteral("%1button_color_left").arg(player_prefix),
Settings::JOYCON_BUTTONS_NEON_BLUE) Settings::JOYCON_BUTTONS_NEON_BLUE)
.toUInt(); .toUInt();
player.button_color_right = player.button_color_right =
qt_config qt_config
->value(QStringLiteral("player_%1_button_color_right").arg(p), ->value(QStringLiteral("%1button_color_right").arg(player_prefix),
Settings::JOYCON_BUTTONS_NEON_RED) Settings::JOYCON_BUTTONS_NEON_RED)
.toUInt(); .toUInt();
}
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
InputCommon::GenerateKeyboardParam(default_buttons[i]);
auto& player_buttons = player.buttons[i]; auto& player_buttons = player.buttons[i];
player_buttons = qt_config player_buttons = qt_config
->value(QStringLiteral("player_%1_").arg(p) + ->value(QStringLiteral("%1").arg(player_prefix) +
QString::fromUtf8(Settings::NativeButton::mapping[i]), QString::fromUtf8(Settings::NativeButton::mapping[i]),
QString::fromStdString(default_param)) QString::fromStdString(default_param))
.toString() .toString()
@ -290,12 +325,11 @@ void Config::ReadPlayerValues() {
} }
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
const std::string default_param = const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
InputCommon::GenerateKeyboardParam(default_motions[i]);
auto& player_motions = player.motions[i]; auto& player_motions = player.motions[i];
player_motions = qt_config player_motions = qt_config
->value(QStringLiteral("player_%1_").arg(p) + ->value(QStringLiteral("%1").arg(player_prefix) +
QString::fromUtf8(Settings::NativeMotion::mapping[i]), QString::fromUtf8(Settings::NativeMotion::mapping[i]),
QString::fromStdString(default_param)) QString::fromStdString(default_param))
.toString() .toString()
@ -312,7 +346,7 @@ void Config::ReadPlayerValues() {
auto& player_analogs = player.analogs[i]; auto& player_analogs = player.analogs[i];
player_analogs = qt_config player_analogs = qt_config
->value(QStringLiteral("player_%1_").arg(p) + ->value(QStringLiteral("%1").arg(player_prefix) +
QString::fromUtf8(Settings::NativeAnalog::mapping[i]), QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(default_param)) QString::fromStdString(default_param))
.toString() .toString()
@ -321,7 +355,6 @@ void Config::ReadPlayerValues() {
player_analogs = default_param; player_analogs = default_param;
} }
} }
}
} }
void Config::ReadDebugValues() { void Config::ReadDebugValues() {
@ -436,7 +469,9 @@ void Config::ReadAudioValues() {
void Config::ReadControlValues() { void Config::ReadControlValues() {
qt_config->beginGroup(QStringLiteral("Controls")); qt_config->beginGroup(QStringLiteral("Controls"));
ReadPlayerValues(); for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
ReadPlayerValue(p);
}
ReadDebugValues(); ReadDebugValues();
ReadKeyboardValues(); ReadKeyboardValues();
ReadMouseValues(); ReadMouseValues();
@ -920,36 +955,43 @@ void Config::ReadValues() {
ReadSystemValues(); ReadSystemValues();
} }
void Config::SavePlayerValues() { void Config::SavePlayerValue(std::size_t player_index) {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { const QString player_prefix = [this, player_index] {
const auto& player = Settings::values.players[p]; if (type == ConfigType::InputProfile) {
return QString{};
} else {
return QStringLiteral("player_%1_").arg(player_index);
}
}();
WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false); const auto& player = Settings::values.players[player_index];
WriteSetting(QStringLiteral("player_%1_type").arg(p),
WriteSetting(QStringLiteral("%1type").arg(player_prefix),
static_cast<u8>(player.controller_type), static_cast<u8>(player.controller_type),
static_cast<u8>(Settings::ControllerType::ProController)); static_cast<u8>(Settings::ControllerType::ProController));
WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left, 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); Settings::JOYCON_BODY_NEON_BLUE);
WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right, WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix),
Settings::JOYCON_BODY_NEON_RED); player.body_color_right, Settings::JOYCON_BODY_NEON_RED);
WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left, WriteSetting(QStringLiteral("%1button_color_left").arg(player_prefix),
Settings::JOYCON_BUTTONS_NEON_BLUE); player.button_color_left, Settings::JOYCON_BUTTONS_NEON_BLUE);
WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p), WriteSetting(QStringLiteral("%1button_color_right").arg(player_prefix),
player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED); player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED);
}
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
InputCommon::GenerateKeyboardParam(default_buttons[i]); WriteSetting(QStringLiteral("%1").arg(player_prefix) +
WriteSetting(QStringLiteral("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeButton::mapping[i]), QString::fromStdString(Settings::NativeButton::mapping[i]),
QString::fromStdString(player.buttons[i]), QString::fromStdString(player.buttons[i]),
QString::fromStdString(default_param)); QString::fromStdString(default_param));
} }
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
const std::string default_param = const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
InputCommon::GenerateKeyboardParam(default_motions[i]); WriteSetting(QStringLiteral("%1").arg(player_prefix) +
WriteSetting(QStringLiteral("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeMotion::mapping[i]), QString::fromStdString(Settings::NativeMotion::mapping[i]),
QString::fromStdString(player.motions[i]), QString::fromStdString(player.motions[i]),
QString::fromStdString(default_param)); QString::fromStdString(default_param));
@ -958,12 +1000,11 @@ void Config::SavePlayerValues() {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_stick_mod[i], 0.5f); default_analogs[i][3], default_stick_mod[i], 0.5f);
WriteSetting(QStringLiteral("player_%1_").arg(p) + WriteSetting(QStringLiteral("%1").arg(player_prefix) +
QString::fromStdString(Settings::NativeAnalog::mapping[i]), QString::fromStdString(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(player.analogs[i]), QString::fromStdString(player.analogs[i]),
QString::fromStdString(default_param)); QString::fromStdString(default_param));
} }
}
} }
void Config::SaveDebugValues() { void Config::SaveDebugValues() {
@ -1087,7 +1128,9 @@ void Config::SaveAudioValues() {
void Config::SaveControlValues() { void Config::SaveControlValues() {
qt_config->beginGroup(QStringLiteral("Controls")); qt_config->beginGroup(QStringLiteral("Controls"));
SavePlayerValues(); for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
SavePlayerValue(p);
}
SaveDebugValues(); SaveDebugValues();
SaveMouseValues(); SaveMouseValues();
SaveTouchscreenValues(); SaveTouchscreenValues();
@ -1515,3 +1558,19 @@ void Config::Save() {
Settings::Sanitize(); Settings::Sanitize();
SaveValues(); 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;
}

View File

@ -16,12 +16,24 @@ class QSettings;
class Config { class Config {
public: 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(); ~Config();
void Reload(); void Reload();
void Save(); 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<int, Settings::NativeButton::NumButtons> default_buttons; static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
static const std::array<int, Settings::NativeMotion::NumMotions> default_motions; static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs; static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
@ -34,7 +46,7 @@ public:
private: private:
void ReadValues(); void ReadValues();
void ReadPlayerValues(); void ReadPlayerValue(std::size_t player_index);
void ReadDebugValues(); void ReadDebugValues();
void ReadKeyboardValues(); void ReadKeyboardValues();
void ReadMouseValues(); void ReadMouseValues();
@ -62,7 +74,7 @@ private:
void ReadWebServiceValues(); void ReadWebServiceValues();
void SaveValues(); void SaveValues();
void SavePlayerValues(); void SavePlayerValue(std::size_t player_index);
void SaveDebugValues(); void SaveDebugValues();
void SaveMouseValues(); void SaveMouseValues();
void SaveTouchscreenValues(); void SaveTouchscreenValues();
@ -111,9 +123,9 @@ private:
void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global, void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global,
const QVariant& default_value); const QVariant& default_value);
ConfigType type;
std::unique_ptr<QSettings> qt_config; std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc; std::string qt_config_loc;
bool global; bool global;
}; };

View File

@ -6,9 +6,11 @@
#include "yuzu/configuration/configure_debug_controller.h" #include "yuzu/configuration/configure_debug_controller.h"
ConfigureDebugController::ConfigureDebugController(QWidget* parent, ConfigureDebugController::ConfigureDebugController(QWidget* parent,
InputCommon::InputSubsystem* input_subsystem) InputCommon::InputSubsystem* input_subsystem,
InputProfiles* profiles)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()), : QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()),
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->setupUi(this);
ui->controllerLayout->addWidget(debug_controller); ui->controllerLayout->addWidget(debug_controller);

View File

@ -10,6 +10,8 @@
class QPushButton; class QPushButton;
class InputProfiles;
namespace InputCommon { namespace InputCommon {
class InputSubsystem; class InputSubsystem;
} }
@ -22,8 +24,8 @@ class ConfigureDebugController : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit ConfigureDebugController(QWidget* parent, explicit ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem,
InputCommon::InputSubsystem* input_subsystem); InputProfiles* profiles);
~ConfigureDebugController() override; ~ConfigureDebugController() override;
void ApplyConfiguration(); void ApplyConfiguration();

View File

@ -23,6 +23,7 @@
#include "yuzu/configuration/configure_motion_touch.h" #include "yuzu/configuration/configure_motion_touch.h"
#include "yuzu/configuration/configure_mouse_advanced.h" #include "yuzu/configuration/configure_mouse_advanced.h"
#include "yuzu/configuration/configure_touchscreen_advanced.h" #include "yuzu/configuration/configure_touchscreen_advanced.h"
#include "yuzu/configuration/input_profiles.h"
namespace { namespace {
template <typename Dialog, typename... Args> template <typename Dialog, typename... Args>
@ -64,7 +65,8 @@ void OnDockedModeChanged(bool last_state, bool new_state) {
} }
ConfigureInput::ConfigureInput(QWidget* parent) ConfigureInput::ConfigureInput(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) { : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
profiles(std::make_unique<InputProfiles>()) {
ui->setupUi(this); ui->setupUi(this);
} }
@ -73,14 +75,22 @@ ConfigureInput::~ConfigureInput() = default;
void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
std::size_t max_players) { std::size_t max_players) {
player_controllers = { player_controllers = {
new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem), new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem,
new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem), profiles.get()),
new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem), new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem,
new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem), profiles.get()),
new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem), new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem,
new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem), profiles.get()),
new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem), new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem,
new ConfigureInputPlayer(this, 7, 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 = { player_tabs = {
@ -134,7 +144,7 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced)); ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
ui->tabAdvanced->layout()->addWidget(advanced); ui->tabAdvanced->layout()->addWidget(advanced);
connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] { connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] {
CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem); CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem, profiles.get());
}); });
connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] { connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] {
CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem); CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem);

View File

@ -19,6 +19,8 @@ class QCheckBox;
class QString; class QString;
class QTimer; class QTimer;
class InputProfiles;
namespace InputCommon { namespace InputCommon {
class InputSubsystem; class InputSubsystem;
} }
@ -61,6 +63,8 @@ private:
std::unique_ptr<Ui::ConfigureInput> ui; std::unique_ptr<Ui::ConfigureInput> ui;
std::unique_ptr<InputProfiles> profiles;
std::array<ConfigureInputPlayer*, 8> player_controllers; std::array<ConfigureInputPlayer*, 8> player_controllers;
std::array<QWidget*, 8> player_tabs; std::array<QWidget*, 8> player_tabs;
std::array<QCheckBox*, 8> player_connected; std::array<QCheckBox*, 8> player_connected;

View File

@ -22,6 +22,8 @@
#include "ui_configure_input_player.h" #include "ui_configure_input_player.h"
#include "yuzu/configuration/config.h" #include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input_player.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; 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, ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index,
QWidget* bottom_row, QWidget* bottom_row,
InputCommon::InputSubsystem* input_subsystem_, InputCommon::InputSubsystem* input_subsystem_,
bool debug) InputProfiles* profiles_, bool debug)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
debug(debug), input_subsystem{input_subsystem_}, timeout_timer(std::make_unique<QTimer>()), debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_),
poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row) { timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()),
bottom_row(bottom_row) {
ui->setupUi(this); ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus); 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<int>(&QComboBox::activated), this,
&ConfigureInputPlayer::LoadProfile);
connect(ui->buttonProfilesSave, &QPushButton::clicked, this,
&ConfigureInputPlayer::SaveProfile);
LoadConfiguration(); LoadConfiguration();
// TODO(wwylele): enable this when we actually emulate it // TODO(wwylele): enable this when we actually emulate it
@ -1061,3 +1075,94 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
SetPollingResult({}, true); 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);
}

View File

@ -26,6 +26,8 @@ class QString;
class QTimer; class QTimer;
class QWidget; class QWidget;
class InputProfiles;
namespace InputCommon { namespace InputCommon {
class InputSubsystem; class InputSubsystem;
} }
@ -45,7 +47,7 @@ class ConfigureInputPlayer : public QWidget {
public: public:
explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row,
InputCommon::InputSubsystem* input_subsystem_, InputCommon::InputSubsystem* input_subsystem_,
bool debug = false); InputProfiles* profiles_, bool debug = false);
~ConfigureInputPlayer() override; ~ConfigureInputPlayer() override;
/// Save all button configurations to settings file. /// 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. /// Gets the default controller mapping for this device and auto configures the input to match.
void UpdateMappingWithDefaults(); 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::ConfigureInputPlayer> ui; std::unique_ptr<Ui::ConfigureInputPlayer> ui;
std::size_t player_index; std::size_t player_index;
@ -123,6 +140,8 @@ private:
InputCommon::InputSubsystem* input_subsystem; InputCommon::InputSubsystem* input_subsystem;
InputProfiles* profiles;
std::unique_ptr<QTimer> timeout_timer; std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer; std::unique_ptr<QTimer> poll_timer;

View File

@ -29,7 +29,8 @@
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id) ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id)
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) { : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) {
game_config = std::make_unique<Config>(fmt::format("{:016X}.ini", title_id), false); game_config = std::make_unique<Config>(fmt::format("{:016X}", title_id),
Config::ConfigType::PerGameConfig);
Settings::SetConfiguringGlobal(false); Settings::SetConfiguringGlobal(false);

View File

@ -0,0 +1,131 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/format.h>
#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<Config>(GetNameWithoutExtension(filename),
Config::ConfigType::InputProfile));
}
return true;
});
}
InputProfiles::~InputProfiles() = default;
std::vector<std::string> InputProfiles::GetInputProfileNames() {
std::vector<std::string> 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<Config>(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();
}

View File

@ -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 <string>
#include <string_view>
#include <unordered_map>
class Config;
class InputProfiles {
public:
explicit InputProfiles();
virtual ~InputProfiles();
std::vector<std::string> 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<std::string, std::unique_ptr<Config>> map_profiles;
};

View File

@ -1087,7 +1087,7 @@ void GMainWindow::BootGame(const QString& filename) {
const auto loader = Loader::GetLoader(v_file); const auto loader = Loader::GetLoader(v_file);
if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) { if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) {
// Load per game settings // 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(); Settings::LogSettings();