diff options
Diffstat (limited to 'src/yuzu/configuration/configure_input_player.cpp')
| -rw-r--r-- | src/yuzu/configuration/configure_input_player.cpp | 1119 |
1 files changed, 870 insertions, 249 deletions
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 13ecb3dc5..21d0d3449 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -18,11 +18,17 @@ #include "core/hle/service/sm/sm.h" #include "input_common/gcadapter/gc_poller.h" #include "input_common/main.h" +#include "input_common/mouse/mouse_poller.h" +#include "input_common/udp/udp.h" #include "ui_configure_input_player.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_input_player_widget.h" +#include "yuzu/configuration/configure_vibration.h" +#include "yuzu/configuration/input_profiles.h" +#include "yuzu/util/limitable_input_dialog.h" -constexpr std::size_t HANDHELD_INDEX = 8; +using namespace Service::HID; const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM> ConfigureInputPlayer::analog_sub_buttons{{ @@ -34,6 +40,8 @@ const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM> namespace { +constexpr std::size_t HANDHELD_INDEX = 8; + void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, bool connected) { Core::System& system{Core::System::GetInstance()}; @@ -42,48 +50,12 @@ void UpdateController(Settings::ControllerType controller_type, std::size_t npad } Service::SM::ServiceManager& sm = system.ServiceManager(); - auto& npad = - sm.GetService<Service::HID::Hid>("hid") - ->GetAppletResource() - ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); + auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>( + HidController::NPad); npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); } -/// Maps the controller type combobox index to Controller Type enum -constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) { - switch (index) { - case 0: - default: - return Settings::ControllerType::ProController; - case 1: - return Settings::ControllerType::DualJoyconDetached; - case 2: - return Settings::ControllerType::LeftJoycon; - case 3: - return Settings::ControllerType::RightJoycon; - case 4: - return Settings::ControllerType::Handheld; - } -} - -/// Maps the Controller Type enum to controller type combobox index -constexpr int GetIndexFromControllerType(Settings::ControllerType type) { - switch (type) { - case Settings::ControllerType::ProController: - default: - return 0; - case Settings::ControllerType::DualJoyconDetached: - return 1; - case Settings::ControllerType::LeftJoycon: - return 2; - case Settings::ControllerType::RightJoycon: - return 3; - case Settings::ControllerType::Handheld: - return 4; - } -} - QString GetKeyName(int key_code) { switch (key_code) { case Qt::LeftButton: @@ -149,6 +121,14 @@ QString ButtonToText(const Common::ParamPackage& param) { return GetKeyName(param.Get("code", 0)); } + if (param.Get("engine", "") == "cemuhookudp") { + if (param.Has("pad_index")) { + const QString motion_str = QString::fromStdString(param.Get("pad_index", "")); + return QObject::tr("Motion %1").arg(motion_str); + } + return GetKeyName(param.Get("code", 0)); + } + if (param.Get("engine", "") == "sdl") { if (param.Has("hat")) { const QString hat_str = QString::fromStdString(param.Get("hat", "")); @@ -173,6 +153,14 @@ QString ButtonToText(const Common::ParamPackage& param) { return {}; } + if (param.Get("engine", "") == "mouse") { + if (param.Has("button")) { + const QString button_str = QString::number(int(param.Get("button", 0))); + return QObject::tr("Click %1").arg(button_str); + } + return GetKeyName(param.Get("code", 0)); + } + return QObject::tr("[unknown]"); } @@ -185,41 +173,31 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); } - if (param.Get("engine", "") == "sdl") { + const auto engine_str = param.Get("engine", ""); + const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + const bool invert_x = param.Get("invert_x", "+") == "-"; + const bool invert_y = param.Get("invert_y", "+") == "-"; + if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse") { if (dir == "modifier") { return QObject::tr("[unused]"); } - if (dir == "left" || dir == "right") { - const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); - - return QObject::tr("Axis %1").arg(axis_x_str); + if (dir == "left") { + const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); } - - if (dir == "up" || dir == "down") { - const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); - - return QObject::tr("Axis %1").arg(axis_y_str); + if (dir == "right") { + const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); } - - return {}; - } - - if (param.Get("engine", "") == "gcpad") { - if (dir == "modifier") { - return QObject::tr("[unused]"); - } - - if (dir == "left" || dir == "right") { - const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); - - return QObject::tr("GC Axis %1").arg(axis_x_str); + if (dir == "up") { + const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); } - - if (dir == "up" || dir == "down") { - const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); - - return QObject::tr("GC Axis %1").arg(axis_y_str); + if (dir == "down") { + const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); } return {}; @@ -231,10 +209,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<Ui::ConfigureInputPlayer>()), player_index(player_index), - debug(debug), input_subsystem{input_subsystem_}, timeout_timer(std::make_unique<QTimer>()), - poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row) { + debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_), + timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()), + bottom_row(bottom_row) { ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -262,19 +241,26 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i }, }}; + motion_map = { + ui->buttonMotionLeft, + ui->buttonMotionRight, + }; + analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone}; analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone}; analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup}; + analog_map_modifier_button = {ui->buttonLStickMod, ui->buttonRStickMod}; analog_map_modifier_label = {ui->labelLStickModifierRange, ui->labelRStickModifierRange}; analog_map_modifier_slider = {ui->sliderLStickModifierRange, ui->sliderRStickModifierRange}; analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup}; analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange}; - const auto ConfigureButtonClick = [&](QPushButton* button, Common::ParamPackage* param, - int default_val) { + const auto ConfigureButtonClick = [&](QPushButton* button, std::size_t button_id, + Common::ParamPackage* param, int default_val, + InputCommon::Polling::DeviceType type) { connect(button, &QPushButton::clicked, [=, this] { HandleClick( - button, + button, button_id, [=, this](Common::ParamPackage params) { // Workaround for ZL & ZR for analog triggers like on XBOX // controllers. Analog triggers (from controllers like the XBOX @@ -291,22 +277,56 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } *param = std::move(params); }, - InputCommon::Polling::DeviceType::Button); + type); }); }; for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { auto* const button = button_map[button_id]; + if (button == nullptr) { continue; } - ConfigureButtonClick(button_map[button_id], &buttons_param[button_id], - Config::default_buttons[button_id]); + + ConfigureButtonClick(button_map[button_id], button_id, &buttons_param[button_id], + Config::default_buttons[button_id], + InputCommon::Polling::DeviceType::Button); + + button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + buttons_param[button_id].Clear(); + button_map[button_id]->setText(tr("[not set]")); + }); + context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); + }); } - // Handle clicks for the modifier buttons as well. - ConfigureButtonClick(ui->buttonLStickMod, &lstick_mod, Config::default_stick_mod[0]); - ConfigureButtonClick(ui->buttonRStickMod, &rstick_mod, Config::default_stick_mod[1]); + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + auto* const button = motion_map[motion_id]; + if (button == nullptr) { + continue; + } + + ConfigureButtonClick(motion_map[motion_id], motion_id, &motions_param[motion_id], + Config::default_motions[motion_id], + InputCommon::Polling::DeviceType::Motion); + + button->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + motions_param[motion_id].Clear(); + motion_map[motion_id]->setText(tr("[not set]")); + }); + context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location)); + }); + } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { @@ -317,26 +337,97 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } connect(analog_button, &QPushButton::clicked, [=, this] { + if (!map_analog_stick_accepted) { + map_analog_stick_accepted = + QMessageBox::information( + this, tr("Map Analog Stick"), + tr("After pressing OK, first move your joystick horizontally, and then " + "vertically.\nTo invert the axes, first move your joystick " + "vertically, and then horizontally."), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok; + if (!map_analog_stick_accepted) { + return; + } + } HandleClick( - analog_map_buttons[analog_id][sub_button_id], + analog_map_buttons[analog_id][sub_button_id], analog_id, [=, this](const Common::ParamPackage& params) { SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); }, InputCommon::Polling::DeviceType::AnalogPreferred); }); + + analog_button->setContextMenuPolicy(Qt::CustomContextMenu); + + connect( + analog_button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + analogs_param[analog_id].Clear(); + analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Invert axis"), [&] { + if (sub_button_id == 2 || sub_button_id == 3) { + const bool invert_value = + analogs_param[analog_id].Get("invert_x", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + analogs_param[analog_id].Set("invert_x", invert_str); + } + if (sub_button_id == 0 || sub_button_id == 1) { + const bool invert_value = + analogs_param[analog_id].Get("invert_y", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + analogs_param[analog_id].Set("invert_y", invert_str); + } + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; + ++sub_button_id) { + analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( + analogs_param[analog_id], analog_sub_buttons[sub_button_id])); + } + }); + context_menu.exec( + analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(menu_location)); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); + }); } + // Handle clicks for the modifier buttons as well. + connect(analog_map_modifier_button[analog_id], &QPushButton::clicked, [=, this] { + HandleClick( + analog_map_modifier_button[analog_id], analog_id, + [=, this](const Common::ParamPackage& params) { + analogs_param[analog_id].Set("modifier", params.Serialize()); + }, + InputCommon::Polling::DeviceType::Button); + }); + + analog_map_modifier_button[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(analog_map_modifier_button[analog_id], &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + analogs_param[analog_id].Set("modifier", ""); + analog_map_modifier_button[analog_id]->setText(tr("[not set]")); + }); + context_menu.exec( + analog_map_modifier_button[analog_id]->mapToGlobal(menu_location)); + }); + connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged), [=, this] { const auto spinbox_value = analog_map_range_spinbox[analog_id]->value(); analogs_param[analog_id].Set("range", spinbox_value / 100.0f); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); }); connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] { const auto slider_value = analog_map_deadzone_slider[analog_id]->value(); analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value)); analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); }); connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] { @@ -348,21 +439,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } // Player Connected checkbox - connect(ui->groupConnectedController, &QGroupBox::toggled, - [this](bool checked) { emit Connected(checked); }); - - // Set up controller type. Only Player 1 can choose Handheld. - ui->comboControllerType->clear(); - - QStringList controller_types = { - tr("Pro Controller"), - tr("Dual Joycons"), - tr("Left Joycon"), - tr("Right Joycon"), - }; + connect(ui->groupConnectedController, &QGroupBox::toggled, [this](bool checked) { + emit Connected(checked); + ui->controllerFrame->SetConnectedStatus(checked); + }); if (player_index == 0) { - controller_types.append(tr("Handheld")); connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) { emit HandheldStateChanged(GetControllerTypeFromIndex(index) == @@ -370,31 +452,38 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i }); } + if (debug || player_index == 9) { + ui->groupConnectedController->setCheckable(false); + } + // The Debug Controller can only choose the Pro Controller. if (debug) { ui->buttonScreenshot->setEnabled(false); ui->buttonHome->setEnabled(false); - ui->groupConnectedController->setCheckable(false); - QStringList debug_controller_types = { - tr("Pro Controller"), - }; - ui->comboControllerType->addItems(debug_controller_types); + ui->comboControllerType->addItem(tr("Pro Controller")); } else { - ui->comboControllerType->addItems(controller_types); + SetConnectableControllers(); } UpdateControllerIcon(); UpdateControllerAvailableButtons(); + UpdateControllerEnabledButtons(); + UpdateControllerButtonNames(); + UpdateMotionButtons(); connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { UpdateControllerIcon(); UpdateControllerAvailableButtons(); + UpdateControllerEnabledButtons(); + UpdateControllerButtonNames(); + UpdateMotionButtons(); }); - connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this, + connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this, &ConfigureInputPlayer::UpdateMappingWithDefaults); + ui->comboDevices->setCurrentIndex(-1); + ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); - UpdateInputDevices(); connect(ui->buttonRefreshDevices, &QPushButton::clicked, [this] { emit RefreshInputDevices(); }); @@ -405,37 +494,82 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i Common::ParamPackage params; if (input_subsystem->GetGCButtons()->IsPolling()) { params = input_subsystem->GetGCButtons()->GetNextInput(); - if (params.Has("engine")) { + if (params.Has("engine") && IsInputAcceptable(params)) { SetPollingResult(params, false); return; } } if (input_subsystem->GetGCAnalogs()->IsPolling()) { params = input_subsystem->GetGCAnalogs()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetUDPMotions()->IsPolling()) { + params = input_subsystem->GetUDPMotions()->GetNextInput(); if (params.Has("engine")) { SetPollingResult(params, false); return; } } + if (input_subsystem->GetMouseButtons()->IsPolling()) { + params = input_subsystem->GetMouseButtons()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseAnalogs()->IsPolling()) { + params = input_subsystem->GetMouseAnalogs()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseMotions()->IsPolling()) { + params = input_subsystem->GetMouseMotions()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseTouch()->IsPolling()) { + params = input_subsystem->GetMouseTouch()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } for (auto& poller : device_pollers) { params = poller->GetNextInput(); - if (params.Has("engine")) { + if (params.Has("engine") && IsInputAcceptable(params)) { SetPollingResult(params, false); return; } } }); - LoadConfiguration(); + UpdateInputProfiles(); - // TODO(wwylele): enable this when we actually emulate it - ui->buttonHome->setEnabled(false); + 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(); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); + ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked()); } ConfigureInputPlayer::~ConfigureInputPlayer() = default; void ConfigureInputPlayer::ApplyConfiguration() { - auto& player = Settings::values.players[player_index]; + auto& player = Settings::values.players.GetValue()[player_index]; auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons; auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs; @@ -448,30 +582,98 @@ void ConfigureInputPlayer::ApplyConfiguration() { return; } - player.controller_type = - static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex()); - player.connected = ui->groupConnectedController->isChecked(); + auto& motions = player.motions; + + std::transform(motions_param.begin(), motions_param.end(), motions.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + + // Apply configuration for handheld + if (player_index == 0) { + auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; + const auto handheld_connected = handheld.connected; + handheld = player; + handheld.connected = handheld_connected; + } +} + +void ConfigureInputPlayer::TryConnectSelectedController() { + auto& player = Settings::values.players.GetValue()[player_index]; - // Player 2-8 - if (player_index != 0) { - UpdateController(player.controller_type, player_index, player.connected); + const auto controller_type = + GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + const auto player_connected = ui->groupConnectedController->isChecked() && + controller_type != Settings::ControllerType::Handheld; + + // Connect Handheld depending on Player 1's controller configuration. + if (player_index == 0) { + auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; + const auto handheld_connected = ui->groupConnectedController->isChecked() && + controller_type == Settings::ControllerType::Handheld; + // Connect only if handheld is going from disconnected to connected + if (!handheld.connected && handheld_connected) { + UpdateController(controller_type, HANDHELD_INDEX, true); + } + handheld.connected = handheld_connected; + } + + if (player.controller_type == controller_type && player.connected == player_connected) { + // Set vibration devices in the event that the input device has changed. + ConfigureVibration::SetVibrationDevices(player_index); return; } - // Player 1 and Handheld - auto& handheld = Settings::values.players[HANDHELD_INDEX]; - // If Handheld is selected, copy all the settings from Player 1 to Handheld. - if (player.controller_type == Settings::ControllerType::Handheld) { - handheld = player; - handheld.connected = ui->groupConnectedController->isChecked(); - player.connected = false; // Disconnect Player 1 - } else { - player.connected = ui->groupConnectedController->isChecked(); - handheld.connected = false; // Disconnect Handheld + player.controller_type = controller_type; + player.connected = player_connected; + + ConfigureVibration::SetVibrationDevices(player_index); + + if (!player.connected) { + return; } - UpdateController(player.controller_type, player_index, player.connected); - UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected); + UpdateController(controller_type, player_index, true); +} + +void ConfigureInputPlayer::TryDisconnectSelectedController() { + const auto& player = Settings::values.players.GetValue()[player_index]; + + const auto controller_type = + GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + const auto player_connected = ui->groupConnectedController->isChecked() && + controller_type != Settings::ControllerType::Handheld; + + // Disconnect Handheld depending on Player 1's controller configuration. + if (player_index == 0 && player.controller_type == Settings::ControllerType::Handheld) { + const auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX]; + const auto handheld_connected = ui->groupConnectedController->isChecked() && + controller_type == Settings::ControllerType::Handheld; + // Disconnect only if handheld is going from connected to disconnected + if (handheld.connected && !handheld_connected) { + UpdateController(controller_type, HANDHELD_INDEX, false); + } + return; + } + + // Do not do anything if the controller configuration has not changed. + if (player.controller_type == controller_type && player.connected == player_connected) { + return; + } + + // Do not disconnect if the controller is already disconnected + if (!player.connected) { + return; + } + + // Disconnect the controller first. + UpdateController(controller_type, player_index, false); +} + +void ConfigureInputPlayer::showEvent(QShowEvent* event) { + if (bottom_row == nullptr) { + return; + } + QWidget::showEvent(event); + ui->main->addWidget(bottom_row); } void ConfigureInputPlayer::changeEvent(QEvent* event) { @@ -488,7 +690,7 @@ void ConfigureInputPlayer::RetranslateUI() { } void ConfigureInputPlayer::LoadConfiguration() { - auto& player = Settings::values.players[player_index]; + auto& player = Settings::values.players.GetValue()[player_index]; if (debug) { std::transform(Settings::values.debug_pad_buttons.begin(), Settings::values.debug_pad_buttons.end(), buttons_param.begin(), @@ -501,71 +703,105 @@ void ConfigureInputPlayer::LoadConfiguration() { [](const std::string& str) { return Common::ParamPackage(str); }); std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(), [](const std::string& str) { return Common::ParamPackage(str); }); + std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); } UpdateUI(); + UpdateInputDeviceCombobox(); if (debug) { return; } - ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type)); + ui->comboControllerType->setCurrentIndex(GetIndexFromControllerType(player.controller_type)); ui->groupConnectedController->setChecked( player.connected || - (player_index == 0 && Settings::values.players[HANDHELD_INDEX].connected)); + (player_index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected)); } -void ConfigureInputPlayer::UpdateInputDevices() { - input_devices = input_subsystem->GetInputDevices(); - ui->comboDevices->clear(); - for (auto device : input_devices) { - ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); - } +void ConfigureInputPlayer::ConnectPlayer(bool connected) { + ui->groupConnectedController->setChecked(connected); } -void ConfigureInputPlayer::RestoreDefaults() { - // Reset Buttons - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; +void ConfigureInputPlayer::UpdateInputDeviceCombobox() { + // Skip input device persistence if "Input Devices" is set to "Any". + if (ui->comboDevices->currentIndex() == 0) { + UpdateInputDevices(); + return; } - // Reset Modifier Buttons - lstick_mod = - Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[0])); - rstick_mod = - Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[1])); + // Find the first button that isn't empty. + const auto button_param = + std::find_if(buttons_param.begin(), buttons_param.end(), + [](const Common::ParamPackage param) { return param.Has("engine"); }); + const bool buttons_empty = button_param == buttons_param.end(); - // Reset Analogs - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; - SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + const auto current_engine = button_param->Get("engine", ""); + const auto current_guid = button_param->Get("guid", ""); + const auto current_port = button_param->Get("port", ""); + + const bool is_keyboard_mouse = current_engine == "keyboard" || current_engine == "mouse"; + + UpdateInputDevices(); + + if (buttons_empty) { + return; + } + + const bool all_one_device = + std::all_of(buttons_param.begin(), buttons_param.end(), + [current_engine, current_guid, current_port, + is_keyboard_mouse](const Common::ParamPackage param) { + if (is_keyboard_mouse) { + return !param.Has("engine") || param.Get("engine", "") == "keyboard" || + param.Get("engine", "") == "mouse"; + } + return !param.Has("engine") || (param.Get("engine", "") == current_engine && + param.Get("guid", "") == current_guid && + param.Get("port", "") == current_port); + }); + + if (all_one_device) { + if (is_keyboard_mouse) { + ui->comboDevices->setCurrentIndex(1); + return; } + const auto devices_it = std::find_if( + input_devices.begin(), input_devices.end(), + [current_engine, current_guid, current_port](const Common::ParamPackage param) { + return param.Get("class", "") == current_engine && + param.Get("guid", "") == current_guid && + param.Get("port", "") == current_port; + }); + const int device_index = + devices_it != input_devices.end() + ? static_cast<int>(std::distance(input_devices.begin(), devices_it)) + : 0; + ui->comboDevices->setCurrentIndex(device_index); + } else { + ui->comboDevices->setCurrentIndex(0); } - UpdateUI(); - UpdateInputDevices(); - ui->comboControllerType->setCurrentIndex(0); +} + +void ConfigureInputPlayer::RestoreDefaults() { + UpdateMappingWithDefaults(); } void ConfigureInputPlayer::ClearAll() { for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { const auto* const button = button_map[button_id]; - if (button == nullptr || !button->isEnabled()) { + if (button == nullptr) { continue; } buttons_param[button_id].Clear(); } - lstick_mod.Clear(); - rstick_mod.Clear(); - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; - if (analog_button == nullptr || !analog_button->isEnabled()) { + if (analog_button == nullptr) { continue; } @@ -573,6 +809,15 @@ void ConfigureInputPlayer::ClearAll() { } } + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + const auto* const motion_button = motion_map[motion_id]; + if (motion_button == nullptr) { + continue; + } + + motions_param[motion_id].Clear(); + } + UpdateUI(); UpdateInputDevices(); } @@ -582,8 +827,9 @@ void ConfigureInputPlayer::UpdateUI() { button_map[button]->setText(ButtonToText(buttons_param[button])); } - ui->buttonLStickMod->setText(ButtonToText(lstick_mod)); - ui->buttonRStickMod->setText(ButtonToText(rstick_mod)); + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id])); + } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { @@ -597,6 +843,9 @@ void ConfigureInputPlayer::UpdateUI() { AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); } + analog_map_modifier_button[analog_id]->setText( + ButtonToText(Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")})); + const auto deadzone_label = analog_map_deadzone_label[analog_id]; const auto deadzone_slider = analog_map_deadzone_slider[analog_id]; const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id]; @@ -607,8 +856,9 @@ void ConfigureInputPlayer::UpdateUI() { int slider_value; auto& param = analogs_param[analog_id]; - const bool is_controller = - param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad"; + const bool is_controller = param.Get("engine", "") == "sdl" || + param.Get("engine", "") == "gcpad" || + param.Get("engine", "") == "mouse"; if (is_controller) { if (!param.Has("deadzone")) { @@ -636,110 +886,98 @@ void ConfigureInputPlayer::UpdateUI() { modifier_label->setVisible(!is_controller); modifier_slider->setVisible(!is_controller); range_groupbox->setVisible(is_controller); + ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); } } -void ConfigureInputPlayer::UpdateMappingWithDefaults() { - if (ui->comboDevices->currentIndex() < 2) { - return; - } - const auto& device = input_devices[ui->comboDevices->currentIndex()]; - auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); - auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); - for (int i = 0; i < buttons_param.size(); ++i) { - buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)]; - } - for (int i = 0; i < analogs_param.size(); ++i) { - analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)]; - } +void ConfigureInputPlayer::SetConnectableControllers() { + const auto add_controllers = [this](bool enable_all, + Controller_NPad::NpadStyleSet npad_style_set = {}) { + index_controller_type_pairs.clear(); + ui->comboControllerType->clear(); - UpdateUI(); -} + if (enable_all || npad_style_set.fullkey == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::ProController); + ui->comboControllerType->addItem(tr("Pro Controller")); + } -void ConfigureInputPlayer::HandleClick( - QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, - InputCommon::Polling::DeviceType type) { - button->setText(tr("[waiting]")); - button->setFocus(); + if (enable_all || npad_style_set.joycon_dual == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::DualJoyconDetached); + ui->comboControllerType->addItem(tr("Dual Joycons")); + } - // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a - // controller, then they don't want keyboard/mouse input - want_keyboard_mouse = ui->comboDevices->currentIndex() < 2; + if (enable_all || npad_style_set.joycon_left == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::LeftJoycon); + ui->comboControllerType->addItem(tr("Left Joycon")); + } - input_setter = new_input_setter; + if (enable_all || npad_style_set.joycon_right == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::RightJoycon); + ui->comboControllerType->addItem(tr("Right Joycon")); + } - device_pollers = input_subsystem->GetPollers(type); + if (player_index == 0 && (enable_all || npad_style_set.handheld == 1)) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::Handheld); + ui->comboControllerType->addItem(tr("Handheld")); + } - for (auto& poller : device_pollers) { - poller->Start(); - } + if (enable_all || npad_style_set.gamecube == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::GameCube); + ui->comboControllerType->addItem(tr("GameCube Controller")); + } + }; - QWidget::grabMouse(); - QWidget::grabKeyboard(); + Core::System& system{Core::System::GetInstance()}; - if (type == InputCommon::Polling::DeviceType::Button) { - input_subsystem->GetGCButtons()->BeginConfiguration(); - } else { - input_subsystem->GetGCAnalogs()->BeginConfiguration(); + if (!system.IsPoweredOn()) { + add_controllers(true); + return; } - timeout_timer->start(2500); // Cancel after 2.5 seconds - poll_timer->start(50); // Check for new inputs every 50ms -} + Service::SM::ServiceManager& sm = system.ServiceManager(); -void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) { - timeout_timer->stop(); - poll_timer->stop(); - for (auto& poller : device_pollers) { - poller->Stop(); - } + auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>( + HidController::NPad); - QWidget::releaseMouse(); - QWidget::releaseKeyboard(); + add_controllers(false, npad.GetSupportedStyleSet()); +} - input_subsystem->GetGCButtons()->EndConfiguration(); - input_subsystem->GetGCAnalogs()->EndConfiguration(); +Settings::ControllerType ConfigureInputPlayer::GetControllerTypeFromIndex(int index) const { + const auto it = + std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(), + [index](const auto& pair) { return pair.first == index; }); - if (!abort) { - (*input_setter)(params); + if (it == index_controller_type_pairs.end()) { + return Settings::ControllerType::ProController; } - UpdateUI(); - input_setter = std::nullopt; + return it->second; } -void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { - if (!input_setter || !event) { - return; - } +int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType type) const { + const auto it = + std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(), + [type](const auto& pair) { return pair.second == type; }); - if (want_keyboard_mouse) { - SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())}, - false); - } else { - // We don't want any mouse buttons, so don't stop polling - return; + if (it == index_controller_type_pairs.end()) { + return -1; } - SetPollingResult({}, true); + return it->first; } -void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { - if (!input_setter || !event) { - return; - } - - if (event->key() != Qt::Key_Escape) { - if (want_keyboard_mouse) { - SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, - false); - } else { - // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling - return; - } +void ConfigureInputPlayer::UpdateInputDevices() { + input_devices = input_subsystem->GetInputDevices(); + ui->comboDevices->clear(); + for (auto device : input_devices) { + ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); } - - SetPollingResult({}, true); } void ConfigureInputPlayer::UpdateControllerIcon() { @@ -762,7 +1000,7 @@ void ConfigureInputPlayer::UpdateControllerIcon() { } }(); - const QString theme = [this] { + const QString theme = [] { if (QIcon::themeName().contains(QStringLiteral("dark"))) { return QStringLiteral("_dark"); } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { @@ -771,8 +1009,8 @@ void ConfigureInputPlayer::UpdateControllerIcon() { return QString{}; } }(); - - ui->controllerFrame->setStyleSheet(stylesheet.arg(theme)); + ui->controllerFrame->SetControllerType( + GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())); } void ConfigureInputPlayer::UpdateControllerAvailableButtons() { @@ -783,7 +1021,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { // List of all the widgets that will be hidden by any of the following layouts that need // "unhidden" after the controller type changes - const std::array<QWidget*, 9> layout_show = { + const std::array<QWidget*, 11> layout_show = { ui->buttonShoulderButtonsSLSR, ui->horizontalSpacerShoulderButtonsWidget, ui->horizontalSpacerShoulderButtonsWidget2, @@ -793,6 +1031,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { ui->buttonShoulderButtonsRight, ui->buttonMiscButtonsPlusHome, ui->bottomRight, + ui->buttonMiscButtonsMinusGroup, + ui->buttonMiscButtonsScreenshotGroup, }; for (auto* widget : layout_show) { @@ -825,6 +1065,14 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { ui->bottomLeft, }; break; + case Settings::ControllerType::GameCube: + layout_hidden = { + ui->buttonShoulderButtonsSLSR, + ui->horizontalSpacerShoulderButtonsWidget2, + ui->buttonMiscButtonsMinusGroup, + ui->buttonMiscButtonsScreenshotGroup, + }; + break; } for (auto* widget : layout_hidden) { @@ -832,14 +1080,387 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() { } } -void ConfigureInputPlayer::showEvent(QShowEvent* event) { - if (bottom_row == nullptr) { +void ConfigureInputPlayer::UpdateControllerEnabledButtons() { + auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + if (debug) { + layout = Settings::ControllerType::ProController; + } + + // List of all the widgets that will be disabled by any of the following layouts that need + // "enabled" after the controller type changes + const std::array<QWidget*, 4> layout_enable = { + ui->buttonHome, + ui->buttonLStickPressedGroup, + ui->groupRStickPressed, + ui->buttonShoulderButtonsButtonLGroup, + }; + + for (auto* widget : layout_enable) { + widget->setEnabled(true); + } + + std::vector<QWidget*> layout_disable; + switch (layout) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::DualJoyconDetached: + case Settings::ControllerType::Handheld: + case Settings::ControllerType::LeftJoycon: + case Settings::ControllerType::RightJoycon: + // TODO(wwylele): enable this when we actually emulate it + layout_disable = { + ui->buttonHome, + }; + break; + case Settings::ControllerType::GameCube: + layout_disable = { + ui->buttonHome, + ui->buttonLStickPressedGroup, + ui->groupRStickPressed, + ui->buttonShoulderButtonsButtonLGroup, + }; + break; + } + + for (auto* widget : layout_disable) { + widget->setEnabled(false); + } +} + +void ConfigureInputPlayer::UpdateMotionButtons() { + if (debug) { + // Motion isn't used with the debug controller, hide both groupboxes. + ui->buttonMotionLeftGroup->hide(); + ui->buttonMotionRightGroup->hide(); return; } - QWidget::showEvent(event); - ui->main->addWidget(bottom_row); + + // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller. + switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::LeftJoycon: + case Settings::ControllerType::Handheld: + // Show "Motion 1" and hide "Motion 2". + ui->buttonMotionLeftGroup->show(); + ui->buttonMotionRightGroup->hide(); + break; + case Settings::ControllerType::RightJoycon: + // Show "Motion 2" and hide "Motion 1". + ui->buttonMotionLeftGroup->hide(); + ui->buttonMotionRightGroup->show(); + break; + case Settings::ControllerType::GameCube: + // Hide both "Motion 1/2". + ui->buttonMotionLeftGroup->hide(); + ui->buttonMotionRightGroup->hide(); + break; + case Settings::ControllerType::DualJoyconDetached: + default: + // Show both "Motion 1/2". + ui->buttonMotionLeftGroup->show(); + ui->buttonMotionRightGroup->show(); + break; + } } -void ConfigureInputPlayer::ConnectPlayer(bool connected) { - ui->groupConnectedController->setChecked(connected); +void ConfigureInputPlayer::UpdateControllerButtonNames() { + auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + if (debug) { + layout = Settings::ControllerType::ProController; + } + + switch (layout) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::DualJoyconDetached: + case Settings::ControllerType::Handheld: + case Settings::ControllerType::LeftJoycon: + case Settings::ControllerType::RightJoycon: + ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus")); + ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL")); + ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR")); + ui->buttonShoulderButtonsRGroup->setTitle(tr("R")); + ui->LStick->setTitle(tr("Left Stick")); + ui->RStick->setTitle(tr("Right Stick")); + break; + case Settings::ControllerType::GameCube: + ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause")); + ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L")); + ui->buttonShoulderButtonsZRGroup->setTitle(tr("R")); + ui->buttonShoulderButtonsRGroup->setTitle(tr("Z")); + ui->LStick->setTitle(tr("Control Stick")); + ui->RStick->setTitle(tr("C-Stick")); + break; + } +} + +void ConfigureInputPlayer::UpdateMappingWithDefaults() { + if (ui->comboDevices->currentIndex() == 0) { + return; + } + + if (ui->comboDevices->currentIndex() == 1) { + // Reset keyboard bindings + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { + buttons_param[button_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + } + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + Common::ParamPackage params{InputCommon::GenerateKeyboardParam( + Config::default_analogs[analog_id][sub_button_id])}; + SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + } + + analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam( + Config::default_stick_mod[analog_id])); + } + + for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { + motions_param[motion_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])}; + } + + UpdateUI(); + return; + } + + // Reset controller bindings + const auto& device = input_devices[ui->comboDevices->currentIndex()]; + auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); + auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); + for (std::size_t i = 0; i < buttons_param.size(); ++i) { + buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)]; + } + for (std::size_t i = 0; i < analogs_param.size(); ++i) { + analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)]; + } + + UpdateUI(); +} + +void ConfigureInputPlayer::HandleClick( + QPushButton* button, std::size_t button_id, + std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::DeviceType type) { + if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) { + button->setText(tr("Shake!")); + } else { + button->setText(tr("[waiting]")); + } + button->setFocus(); + + // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a + // controller, then they don't want keyboard/mouse input + want_keyboard_mouse = ui->comboDevices->currentIndex() < 2; + + input_setter = new_input_setter; + + device_pollers = input_subsystem->GetPollers(type); + + for (auto& poller : device_pollers) { + poller->Start(); + } + + QWidget::grabMouse(); + QWidget::grabKeyboard(); + + if (type == InputCommon::Polling::DeviceType::Button) { + input_subsystem->GetGCButtons()->BeginConfiguration(); + } else { + input_subsystem->GetGCAnalogs()->BeginConfiguration(); + } + + if (type == InputCommon::Polling::DeviceType::Motion) { + input_subsystem->GetUDPMotions()->BeginConfiguration(); + } + + if (type == InputCommon::Polling::DeviceType::Button) { + input_subsystem->GetMouseButtons()->BeginConfiguration(); + } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) { + input_subsystem->GetMouseAnalogs()->BeginConfiguration(); + } else if (type == InputCommon::Polling::DeviceType::Motion) { + input_subsystem->GetMouseMotions()->BeginConfiguration(); + } else { + input_subsystem->GetMouseTouch()->BeginConfiguration(); + } + + if (type == InputCommon::Polling::DeviceType::Button) { + ui->controllerFrame->BeginMappingButton(button_id); + } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) { + ui->controllerFrame->BeginMappingAnalog(button_id); + } + + timeout_timer->start(2500); // Cancel after 2.5 seconds + poll_timer->start(50); // Check for new inputs every 50ms +} + +void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) { + timeout_timer->stop(); + poll_timer->stop(); + for (auto& poller : device_pollers) { + poller->Stop(); + } + + QWidget::releaseMouse(); + QWidget::releaseKeyboard(); + + input_subsystem->GetGCButtons()->EndConfiguration(); + input_subsystem->GetGCAnalogs()->EndConfiguration(); + + input_subsystem->GetUDPMotions()->EndConfiguration(); + + input_subsystem->GetMouseButtons()->EndConfiguration(); + input_subsystem->GetMouseAnalogs()->EndConfiguration(); + input_subsystem->GetMouseMotions()->EndConfiguration(); + input_subsystem->GetMouseTouch()->EndConfiguration(); + + if (!abort) { + (*input_setter)(params); + } + + UpdateUI(); + UpdateInputDeviceCombobox(); + ui->controllerFrame->EndMapping(); + + input_setter = std::nullopt; +} + +bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const { + if (ui->comboDevices->currentIndex() == 0) { + return true; + } + + // Keyboard/Mouse + if (ui->comboDevices->currentIndex() == 1) { + return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse"; + } + + const auto current_input_device = input_devices[ui->comboDevices->currentIndex()]; + return params.Get("engine", "") == current_input_device.Get("class", "") && + params.Get("guid", "") == current_input_device.Get("guid", "") && + params.Get("port", "") == current_input_device.Get("port", ""); +} + +void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { + if (!input_setter || !event) { + return; + } + + input_subsystem->GetMouse()->PressButton(0, 0, event->button()); +} + +void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { + if (!input_setter || !event) { + return; + } + + if (event->key() != Qt::Key_Escape) { + if (want_keyboard_mouse) { + SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, + false); + } else { + // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling + return; + } + } + + 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 (!InputProfiles::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)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); + return; + } + + emit RefreshInputProfiles(player_index); + + 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)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); + return; + } + + emit RefreshInputProfiles(player_index); + + 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)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); + 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)); + UpdateInputProfiles(); + emit RefreshInputProfiles(player_index); + return; + } +} + +void ConfigureInputPlayer::UpdateInputProfiles() { + ui->comboProfiles->clear(); + + for (const auto& profile_name : profiles->GetInputProfileNames()) { + ui->comboProfiles->addItem(QString::fromStdString(profile_name)); + } + + ui->comboProfiles->setCurrentIndex(-1); } |
