diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index 77c7f601f..9f622c308 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -16,6 +16,8 @@ #include "citra_qt/configuration/configure_input.h" #include "citra_qt/configuration/configure_motion_touch.h" #include "common/param_package.h" +#include "core/core.h" +#include "core/hid/emulated_controller.h" #include "ui_configure_input.h" const std::array @@ -24,18 +26,11 @@ const std::array "down", "left", "right", - "modifier", }}; -enum class AnalogSubButtons { - up, - down, - left, - right, - modifier, -}; +namespace { -static QString GetKeyName(int key_code) { +QString GetKeyName(int key_code) { switch (key_code) { case Qt::Key_Shift: return QObject::tr("Shift"); @@ -44,104 +39,222 @@ static QString GetKeyName(int key_code) { case Qt::Key_Alt: return QObject::tr("Alt"); case Qt::Key_Meta: - return QString{}; + return {}; default: return QKeySequence(key_code).toString(); } } -static void SetAnalogButton(const Common::ParamPackage& input_param, - Common::ParamPackage& analog_param, const std::string& button_name) { - if (analog_param.Get("engine", "") != "analog_from_button") { +QString GetButtonName(Common::Input::ButtonNames button_name) { + switch (button_name) { + case Common::Input::ButtonNames::ButtonLeft: + return QObject::tr("Left"); + case Common::Input::ButtonNames::ButtonRight: + return QObject::tr("Right"); + case Common::Input::ButtonNames::ButtonDown: + return QObject::tr("Down"); + case Common::Input::ButtonNames::ButtonUp: + return QObject::tr("Up"); + case Common::Input::ButtonNames::TriggerZ: + return QObject::tr("Z"); + case Common::Input::ButtonNames::TriggerR: + return QObject::tr("R"); + case Common::Input::ButtonNames::TriggerL: + return QObject::tr("L"); + case Common::Input::ButtonNames::ButtonA: + return QObject::tr("A"); + case Common::Input::ButtonNames::ButtonB: + return QObject::tr("B"); + case Common::Input::ButtonNames::ButtonX: + return QObject::tr("X"); + case Common::Input::ButtonNames::ButtonY: + return QObject::tr("Y"); + case Common::Input::ButtonNames::ButtonStart: + return QObject::tr("Start"); + case Common::Input::ButtonNames::L1: + return QObject::tr("L1"); + case Common::Input::ButtonNames::L2: + return QObject::tr("L2"); + case Common::Input::ButtonNames::L3: + return QObject::tr("L3"); + case Common::Input::ButtonNames::R1: + return QObject::tr("R1"); + case Common::Input::ButtonNames::R2: + return QObject::tr("R2"); + case Common::Input::ButtonNames::R3: + return QObject::tr("R3"); + case Common::Input::ButtonNames::Circle: + return QObject::tr("Circle"); + case Common::Input::ButtonNames::Cross: + return QObject::tr("Cross"); + case Common::Input::ButtonNames::Square: + return QObject::tr("Square"); + case Common::Input::ButtonNames::Triangle: + return QObject::tr("Triangle"); + case Common::Input::ButtonNames::Share: + return QObject::tr("Share"); + case Common::Input::ButtonNames::Options: + return QObject::tr("Options"); + case Common::Input::ButtonNames::Home: + return QObject::tr("Home"); + case Common::Input::ButtonNames::Touch: + return QObject::tr("Touch"); + case Common::Input::ButtonNames::ButtonMouseWheel: + return QObject::tr("Wheel", "Indicates the mouse wheel"); + case Common::Input::ButtonNames::ButtonBackward: + return QObject::tr("Backward"); + case Common::Input::ButtonNames::ButtonForward: + return QObject::tr("Forward"); + case Common::Input::ButtonNames::ButtonTask: + return QObject::tr("Task"); + case Common::Input::ButtonNames::ButtonExtra: + return QObject::tr("Extra"); + default: + return QObject::tr("[undefined]"); + } +} + +QString GetDirectionName(const std::string& direction) { + if (direction == "left") { + return QObject::tr("Left"); + } + if (direction == "right") { + return QObject::tr("Right"); + } + if (direction == "up") { + return QObject::tr("Up"); + } + if (direction == "down") { + return QObject::tr("Down"); + } + UNIMPLEMENTED_MSG("Unimplemented direction name={}", direction); + return QString::fromStdString(direction); +} + +void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param, + const std::string& button_name) { + // The poller returned a complete axis, so set all the buttons + if (input_param.Has("axis_x") && input_param.Has("axis_y")) { + analog_param = input_param; + return; + } + // Check if the current configuration has either no engine or an axis binding. + // Clears out the old binding and adds one with analog_from_button. + if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) { analog_param = { {"engine", "analog_from_button"}, }; } analog_param.Set(button_name, input_param.Serialize()); } +} // namespace -static QString ButtonToText(const Common::ParamPackage& param) { +QString ConfigureInput::ButtonToText(const Common::ParamPackage& param) { if (!param.Has("engine")) { return QObject::tr("[not set]"); } - const auto engine_str = param.Get("engine", ""); - if (engine_str == "keyboard") { - return GetKeyName(param.Get("code", 0)); + + const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); + const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); + const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : ""); + const auto common_button_name = input_subsystem->GetButtonName(param); + + // Retrieve the names from Qt + if (param.Get("engine", "") == "keyboard") { + const QString button_str = GetKeyName(param.Get("code", 0)); + return QObject::tr("%1%2%3").arg(toggle, inverted, button_str); } - if (engine_str == "sdl") { + if (common_button_name == Common::Input::ButtonNames::Invalid) { + return QObject::tr("[invalid]"); + } + + if (common_button_name == Common::Input::ButtonNames::Engine) { + return QString::fromStdString(param.Get("engine", "")); + } + + if (common_button_name == Common::Input::ButtonNames::Value) { if (param.Has("hat")) { - const QString hat_str = QString::fromStdString(param.Get("hat", "")); - const QString direction_str = QString::fromStdString(param.Get("direction", "")); - - return QObject::tr("Hat %1 %2").arg(hat_str, direction_str); + const QString hat = GetDirectionName(param.Get("direction", "")); + return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat); } - if (param.Has("axis")) { - const QString axis_str = QString::fromStdString(param.Get("axis", "")); - const QString direction_str = QString::fromStdString(param.Get("direction", "")); - - return QObject::tr("Axis %1%2").arg(axis_str, direction_str); + const QString axis = QString::fromStdString(param.Get("axis", "")); + return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis); + } + if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { + const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); + const QString axis_y = QString::fromStdString(param.Get("axis_y", "")); + const QString axis_z = QString::fromStdString(param.Get("axis_z", "")); + return QObject::tr("%1%2Axis %3,%4,%5").arg(toggle, inverted, axis_x, axis_y, axis_z); + } + if (param.Has("motion")) { + const QString motion = QString::fromStdString(param.Get("motion", "")); + return QObject::tr("%1%2Motion %3").arg(toggle, inverted, motion); } - if (param.Has("button")) { - const QString button_str = QString::fromStdString(param.Get("button", "")); - - return QObject::tr("Button %1").arg(button_str); + const QString button = QString::fromStdString(param.Get("button", "")); + return QObject::tr("%1%2Button %3").arg(toggle, inverted, button); } - - return {}; } - if (engine_str == "gcpad") { - if (param.Has("axis")) { - const QString axis_str = QString::fromStdString(param.Get("axis", "")); - const QString direction_str = QString::fromStdString(param.Get("direction", "")); - - return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str); - } - if (param.Has("button")) { - const QString button_str = QString::number(int(std::log2(param.Get("button", 0)))); - return QObject::tr("GC Button %1").arg(button_str); - } - return GetKeyName(param.Get("code", 0)); + QString button_name = GetButtonName(common_button_name); + if (param.Has("hat")) { + return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name); + } + if (param.Has("axis")) { + return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); + } + if (param.Has("motion")) { + return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); + } + if (param.Has("button")) { + return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name); } return QObject::tr("[unknown]"); } -static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { +QString ConfigureInput::AnalogToText(const Common::ParamPackage& param, const std::string& dir) { if (!param.Has("engine")) { return QObject::tr("[not set]"); } - const auto engine_str = param.Get("engine", ""); - if (engine_str == "analog_from_button") { + if (param.Get("engine", "") == "analog_from_button") { return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); } - const QString axis_x_str{QString::fromStdString(param.Get("axis_x", ""))}; - const QString axis_y_str{QString::fromStdString(param.Get("axis_y", ""))}; - static const QString plus_str{QString::fromStdString("+")}; - static const QString minus_str{QString::fromStdString("-")}; - if (engine_str == "sdl" || engine_str == "gcpad") { - if (dir == "modifier") { - return QObject::tr("[unused]"); - } - if (dir == "left") { - return QObject::tr("Axis %1%2").arg(axis_x_str, minus_str); - } - if (dir == "right") { - return QObject::tr("Axis %1%2").arg(axis_x_str, plus_str); - } - if (dir == "up") { - return QObject::tr("Axis %1%2").arg(axis_y_str, plus_str); - } - if (dir == "down") { - return QObject::tr("Axis %1%2").arg(axis_y_str, minus_str); - } - return {}; + if (!param.Has("axis_x") || !param.Has("axis_y")) { + return QObject::tr("[unknown]"); } + + 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 (dir == "modifier") { + return QObject::tr("[unused]"); + } + + 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 == "right") { + 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") { + const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_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 QObject::tr("[unknown]"); } @@ -149,6 +262,16 @@ ConfigureInput::ConfigureInput(QWidget* parent) : QWidget(parent), ui(std::make_unique()), timeout_timer(std::make_unique()), poll_timer(std::make_unique()) { ui->setupUi(this); +} + +ConfigureInput::~ConfigureInput() { + emulated_controller->DisableConfiguration(); +}; + +void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem_) { + input_subsystem = input_subsystem_; + emulated_controller = Core::System::GetInstance().HIDCore().GetEmulatedController(); + setFocusPolicy(Qt::ClickFocus); for (const auto& profile : Settings::values.input_profiles) { @@ -167,10 +290,10 @@ ConfigureInput::ConfigureInput(QWidget* parent) analog_map_buttons = {{ { - ui->buttonCircleUp, - ui->buttonCircleDown, - ui->buttonCircleLeft, - ui->buttonCircleRight, + ui->buttonCirclePadUp, + ui->buttonCirclePadDown, + ui->buttonCirclePadLeft, + ui->buttonCirclePadRight, nullptr, }, { @@ -182,230 +305,347 @@ ConfigureInput::ConfigureInput(QWidget* parent) }, }}; - analog_map_stick = {ui->buttonCircleAnalog, ui->buttonCStickAnalog}; - analog_map_deadzone_and_modifier_slider = {ui->sliderCirclePadDeadzoneAndModifier, - ui->sliderCStickDeadzoneAndModifier}; - analog_map_deadzone_and_modifier_slider_label = {ui->labelCirclePadDeadzoneAndModifier, - ui->labelCStickDeadzoneAndModifier}; + analog_map_deadzone_label = {ui->labelCirclePadDeadzone, ui->labelCStickDeadzone}; + analog_map_deadzone_slider = {ui->sliderCirclePadDeadzone, ui->sliderCStickDeadzone}; - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - if (!button_map[button_id]) + analog_map_modifier_groupbox = {ui->buttonCirclePadModGroup, ui->buttonCStickModGroup}; + analog_map_modifier_button = {ui->buttonCirclePadMod, ui->buttonCStickMod}; + analog_map_modifier_label = {ui->labelCirclePadModifierRange, ui->labelCStickModifierRange}; + analog_map_modifier_slider = {ui->sliderCirclePadModifierRange, ui->sliderCStickModifierRange}; + + analog_map_range_groupbox = {ui->buttonCirclePadRangeGroup, ui->buttonCStickRangeGroup}; + analog_map_range_spinbox = {ui->spinboxCirclePadRange, ui->spinboxCStickRange}; + + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { + auto* const button = button_map[button_id]; + + if (button == nullptr) { continue; - button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); - connect(button_map[button_id], &QPushButton::clicked, [this, button_id]() { + } + + connect(button, &QPushButton::clicked, [=, this] { HandleClick( - button_map[button_id], - [this, button_id](Common::ParamPackage params) { - // Workaround for ZL & ZR for analog triggers like on XBOX controllors. - // Analog triggers (from controllers like the XBOX controller) would not - // work due to a different range of their signals (from 0 to 255 on - // analog triggers instead of -32768 to 32768 on analog joysticks). The - // SDL driver misinterprets analog triggers as analog joysticks. - // TODO: reinterpret the signal range for analog triggers to map the - // values correctly. This is required for the correct emulation of the - // analog triggers of the GameCube controller. - if (button_id == Settings::NativeButton::ZL || - button_id == Settings::NativeButton::ZR) { - params.Set("direction", "+"); - params.Set("threshold", "0.5"); - } - buttons_param[button_id] = std::move(params); - // If the user closes the dialog, the changes are reverted in - // `GMainWindow::OnConfigure()` - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + button, button_id, + [=, this](const Common::ParamPackage& params) { + emulated_controller->SetButtonParam(button_id, params); }, - InputCommon::Polling::DeviceType::Button); + InputCommon::Polling::InputType::Button); }); - connect(button_map[button_id], &QPushButton::customContextMenuRequested, this, - [this, button_id](const QPoint& menu_location) { + + button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { QMenu context_menu; - context_menu.addAction(tr("Clear"), this, [&] { - buttons_param[button_id].Clear(); + Common::ParamPackage param = emulated_controller->GetButtonParam(button_id); + context_menu.addAction(tr("Clear"), [&] { + emulated_controller->SetButtonParam(button_id, {}); button_map[button_id]->setText(tr("[not set]")); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); - }); - context_menu.addAction(tr("Restore Default"), this, [&] { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; - button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); }); + if (param.Has("code") || param.Has("button") || param.Has("hat")) { + context_menu.addAction(tr("Toggle button"), [&] { + const bool toggle_value = !param.Get("toggle", false); + param.Set("toggle", toggle_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); + context_menu.addAction(tr("Invert button"), [&] { + const bool invert_value = !param.Get("inverted", false); + param.Set("inverted", invert_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); + } + if (param.Has("axis")) { + context_menu.addAction(tr("Invert axis"), [&] { + const bool toggle_value = !(param.Get("invert", "+") == "-"); + param.Set("invert", toggle_value ? "-" : "+"); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); + context_menu.addAction(tr("Set threshold"), [&] { + const int button_threshold = + static_cast(param.Get("threshold", 0.5f) * 100.0f); + const int new_threshold = QInputDialog::getInt( + this, tr("Set threshold"), tr("Choose a value between 0% and 100%"), + button_threshold, 0, 100); + param.Set("threshold", new_threshold / 100.0f); + + if (button_id == Settings::NativeButton::ZL) { + ui->sliderZLThreshold->setValue(new_threshold); + } + if (button_id == Settings::NativeButton::ZR) { + ui->sliderZRThreshold->setValue(new_threshold); + } + emulated_controller->SetButtonParam(button_id, param); + }); + } context_menu.exec(button_map[button_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++) { - if (!analog_map_buttons[analog_id][sub_button_id]) + connect(ui->sliderZLThreshold, &QSlider::valueChanged, [=, this] { + Common::ParamPackage param = + emulated_controller->GetButtonParam(Settings::NativeButton::ZL); + if (param.Has("threshold")) { + const auto slider_value = ui->sliderZLThreshold->value(); + param.Set("threshold", slider_value / 100.0f); + emulated_controller->SetButtonParam(Settings::NativeButton::ZL, param); + } + }); + + connect(ui->sliderZRThreshold, &QSlider::valueChanged, [=, this] { + Common::ParamPackage param = + emulated_controller->GetButtonParam(Settings::NativeButton::ZR); + if (param.Has("threshold")) { + const auto slider_value = ui->sliderZRThreshold->value(); + param.Set("threshold", slider_value / 100.0f); + emulated_controller->SetButtonParam(Settings::NativeButton::ZR, param); + } + }); + + 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) { + auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; + + if (analog_button == nullptr) { continue; - analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy( - Qt::CustomContextMenu); - connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::clicked, this, - [this, analog_id, sub_button_id]() { - HandleClick( - analog_map_buttons[analog_id][sub_button_id], - [this, analog_id, sub_button_id](const Common::ParamPackage& params) { - SetAnalogButton(params, analogs_param[analog_id], - analog_sub_buttons[sub_button_id]); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); - }, - InputCommon::Polling::DeviceType::Button); - }); - connect(analog_map_buttons[analog_id][sub_button_id], - &QPushButton::customContextMenuRequested, this, - [this, analog_id, sub_button_id](const QPoint& menu_location) { + } + + 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_id, + [=, this](const Common::ParamPackage& params) { + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); + SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]); + // Correct axis direction for inverted sticks + if (input_subsystem->IsStickInverted(param)) { + switch (analog_id) { + case Settings::NativeAnalog::CirclePad: { + const bool invert_value = param.Get("invert_x", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + param.Set("invert_x", invert_str); + break; + } + case Settings::NativeAnalog::CStick: { + const bool invert_value = param.Get("invert_y", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + param.Set("invert_y", invert_str); + break; + } + default: + break; + } + } + emulated_controller->SetStickParam(analog_id, param); + }, + InputCommon::Polling::InputType::Stick); + }); + + analog_button->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(analog_button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { QMenu context_menu; - context_menu.addAction(tr("Clear"), this, [&] { - analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); + context_menu.addAction(tr("Clear"), [&] { + if (param.Get("engine", "") != "analog_from_button") { + emulated_controller->SetStickParam(analog_id, {}); + for (auto button : analog_map_buttons[analog_id]) { + button->setText(tr("[not set]")); + } + return; + } + switch (sub_button_id) { + case 0: + param.Erase("up"); + break; + case 1: + param.Erase("down"); + break; + case 2: + param.Erase("left"); + break; + case 3: + param.Erase("right"); + break; + } + emulated_controller->SetStickParam(analog_id, param); analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); }); - context_menu.addAction(tr("Restore Default"), this, [&] { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; - SetAnalogButton(params, analogs_param[analog_id], - analog_sub_buttons[sub_button_id]); - analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( - analogs_param[analog_id], analog_sub_buttons[sub_button_id])); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + context_menu.addAction(tr("Center axis"), [&] { + const auto stick_value = + emulated_controller->GetSticksValues()[analog_id]; + const float offset_x = stick_value.x.properties.offset; + const float offset_y = stick_value.y.properties.offset; + float raw_value_x = stick_value.x.raw_value; + float raw_value_y = stick_value.y.raw_value; + // See Core::HID::SanitizeStick() to obtain the original raw axis value + if (std::abs(offset_x) < 0.5f) { + if (raw_value_x > 0) { + raw_value_x *= 1 + offset_x; + } else { + raw_value_x *= 1 - offset_x; + } + } + if (std::abs(offset_x) < 0.5f) { + if (raw_value_y > 0) { + raw_value_y *= 1 + offset_y; + } else { + raw_value_y *= 1 - offset_y; + } + } + param.Set("offset_x", -raw_value_x + offset_x); + param.Set("offset_y", -raw_value_y + offset_y); + emulated_controller->SetStickParam(analog_id, param); + }); + context_menu.addAction(tr("Invert axis"), [&] { + if (sub_button_id == 2 || sub_button_id == 3) { + const bool invert_value = param.Get("invert_x", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + param.Set("invert_x", invert_str); + emulated_controller->SetStickParam(analog_id, param); + } + if (sub_button_id == 0 || sub_button_id == 1) { + const bool invert_value = param.Get("invert_y", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + param.Set("invert_y", invert_str); + emulated_controller->SetStickParam(analog_id, param); + } + for (int analog_sub_button_id = 0; + analog_sub_button_id < ANALOG_SUB_BUTTONS_NUM; + ++analog_sub_button_id) { + analog_map_buttons[analog_id][analog_sub_button_id]->setText( + AnalogToText(param, analog_sub_buttons[analog_sub_button_id])); + } }); context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( menu_location)); }); } - connect(analog_map_stick[analog_id], &QPushButton::clicked, this, [this, analog_id]() { - if (QMessageBox::information( - this, tr("Information"), - tr("After pressing OK, first move your joystick horizontally, " - "and then vertically."), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { - HandleClick( - analog_map_stick[analog_id], - [this, analog_id](const Common::ParamPackage& params) { - analogs_param[analog_id] = params; - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); - }, - InputCommon::Polling::DeviceType::Analog); - } + + // 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) { + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); + param.Set("modifier", params.Serialize()); + emulated_controller->SetStickParam(analog_id, param); + }, + InputCommon::Polling::InputType::Button); }); - connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, this, - [this, analog_id] { - const int slider_value = - analog_map_deadzone_and_modifier_slider[analog_id]->value(); - const auto engine = analogs_param[analog_id].Get("engine", ""); - if (engine == "sdl" || engine == "gcpad") { - analog_map_deadzone_and_modifier_slider_label[analog_id]->setText( - tr("Deadzone: %1%").arg(slider_value)); - analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); - } else { - analog_map_deadzone_and_modifier_slider_label[analog_id]->setText( - tr("Modifier Scale: %1%").arg(slider_value)); - analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f); - } - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); - }); - } - // The Circle Mod button is common for both the sticks, so update the modifier settings - // for both the sticks. - connect(ui->buttonCircleMod, &QPushButton::clicked, this, [this]() { - HandleClick( - ui->buttonCircleMod, - [this](const Common::ParamPackage& params) { - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; - analog_id++) { - SetAnalogButton(params, analogs_param[analog_id], "modifier"); - } - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); - }, - InputCommon::Polling::DeviceType::Button); - }); - connect(ui->buttonCircleMod, &QPushButton::customContextMenuRequested, this, - [&](const QPoint& menu_location) { + 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"), this, [&] { - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; - analog_id++) { - analogs_param[analog_id].Erase("modifier"); - } - ui->buttonCircleMod->setText(tr("[not set]")); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); + context_menu.addAction(tr("Clear"), [&] { + param.Set("modifier", ""); + analog_map_modifier_button[analog_id]->setText(tr("[not set]")); + emulated_controller->SetStickParam(analog_id, param); }); - - context_menu.addAction(tr("Restore Default"), this, [&] { - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; - analog_id++) { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id] - [static_cast(AnalogSubButtons::modifier)])}; - SetAnalogButton(params, analogs_param[analog_id], "modifier"); - ui->buttonCircleMod->setText( - AnalogToText(analogs_param[analog_id], "modifier")); - } - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + context_menu.addAction(tr("Toggle button"), [&] { + Common::ParamPackage modifier_param = + Common::ParamPackage{param.Get("modifier", "")}; + const bool toggle_value = !modifier_param.Get("toggle", false); + modifier_param.Set("toggle", toggle_value); + param.Set("modifier", modifier_param.Serialize()); + analog_map_modifier_button[analog_id]->setText(ButtonToText(modifier_param)); + emulated_controller->SetStickParam(analog_id, param); }); - context_menu.exec(ui->buttonCircleMod->mapToGlobal(menu_location)); + context_menu.addAction(tr("Invert button"), [&] { + Common::ParamPackage modifier_param = + Common::ParamPackage{param.Get("modifier", "")}; + const bool invert_value = !modifier_param.Get("inverted", false); + modifier_param.Set("inverted", invert_value); + param.Set("modifier", modifier_param.Serialize()); + analog_map_modifier_button[analog_id]->setText(ButtonToText(modifier_param)); + emulated_controller->SetStickParam(analog_id, param); + }); + context_menu.exec( + analog_map_modifier_button[analog_id]->mapToGlobal(menu_location)); }); - connect(ui->buttonMotionTouch, &QPushButton::clicked, this, [this] { - QDialog* motion_touch_dialog = new ConfigureMotionTouch(this); - return motion_touch_dialog->exec(); - }); + connect(analog_map_range_spinbox[analog_id], qOverload(&QSpinBox::valueChanged), + [=, this] { + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); + const auto spinbox_value = analog_map_range_spinbox[analog_id]->value(); + param.Set("range", spinbox_value / 100.0f); + emulated_controller->SetStickParam(analog_id, param); + }); - ui->buttonDelete->setEnabled(ui->profile->count() > 1); + connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] { + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); + const auto slider_value = analog_map_deadzone_slider[analog_id]->value(); + analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value)); + param.Set("deadzone", slider_value / 100.0f); + emulated_controller->SetStickParam(analog_id, param); + }); - connect(ui->buttonAutoMap, &QPushButton::clicked, this, &ConfigureInput::AutoMap); - connect(ui->buttonClearAll, &QPushButton::clicked, this, &ConfigureInput::ClearAll); - connect(ui->buttonRestoreDefaults, &QPushButton::clicked, this, - &ConfigureInput::RestoreDefaults); - connect(ui->buttonNew, &QPushButton::clicked, this, &ConfigureInput::NewProfile); - connect(ui->buttonDelete, &QPushButton::clicked, this, &ConfigureInput::DeleteProfile); - connect(ui->buttonRename, &QPushButton::clicked, this, &ConfigureInput::RenameProfile); + connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] { + Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); + const auto slider_value = analog_map_modifier_slider[analog_id]->value(); + analog_map_modifier_label[analog_id]->setText( + tr("Modifier Range: %1%").arg(slider_value)); + param.Set("modifier_scale", slider_value / 100.0f); + emulated_controller->SetStickParam(analog_id, param); + }); + } - connect(ui->profile, qOverload(&QComboBox::currentIndexChanged), this, [this](int i) { - ApplyConfiguration(); - Settings::SaveProfile(Settings::values.current_input_profile_index); - Settings::LoadProfile(i); - LoadConfiguration(); - }); + connect(ui->comboDevices, qOverload(&QComboBox::activated), this, + &ConfigureInput::UpdateMappingWithDefaults); + ui->comboDevices->installEventFilter(this); + + ui->comboDevices->setCurrentIndex(-1); timeout_timer->setSingleShot(true); - connect(timeout_timer.get(), &QTimer::timeout, this, [this]() { SetPollingResult({}, true); }); + connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); - connect(poll_timer.get(), &QTimer::timeout, this, [this]() { - Common::ParamPackage params; - for (auto& poller : device_pollers) { - params = poller->GetNextInput(); - if (params.Has("engine")) { - SetPollingResult(params, false); - return; - } + connect(poll_timer.get(), &QTimer::timeout, [this] { + const auto& params = input_subsystem->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; } }); + // UpdateInputProfiles(); + + connect(ui->buttonProfilesNew, &QPushButton::clicked, this, &ConfigureInput::NewProfile); + connect(ui->buttonProfilesDelete, &QPushButton::clicked, this, &ConfigureInput::DeleteProfile); + // connect(ui->comboProfiles, qOverload(&QComboBox::activated), this, + // &ConfigureInput::LoadProfile); + // connect(ui->buttonProfilesSave, &QPushButton::clicked, this, &ConfigureInput::SaveProfile); + + connect(ui->buttonMotionTouch, &QPushButton::clicked, [this, input_subsystem_] { + QDialog* motion_touch_dialog = new ConfigureMotionTouch(this, input_subsystem_); + return motion_touch_dialog->exec(); + }); + connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); + connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); + LoadConfiguration(); } -ConfigureInput::~ConfigureInput() = default; - void ConfigureInput::ApplyConfiguration() { - std::transform(buttons_param.begin(), buttons_param.end(), - Settings::values.current_input_profile.buttons.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); - std::transform(analogs_param.begin(), analogs_param.end(), - Settings::values.current_input_profile.analogs.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); + emulated_controller->DisableConfiguration(); + emulated_controller->SaveCurrentConfig(); + emulated_controller->EnableConfiguration(); } void ConfigureInput::ApplyProfile() { @@ -444,177 +684,233 @@ QList ConfigureInput::GetUsedKeyboardKeys() { } void ConfigureInput::LoadConfiguration() { - std::transform(Settings::values.current_input_profile.buttons.begin(), - Settings::values.current_input_profile.buttons.end(), buttons_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - std::transform(Settings::values.current_input_profile.analogs.begin(), - Settings::values.current_input_profile.analogs.end(), analogs_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - UpdateButtonLabels(); + emulated_controller->ReloadFromSettings(); + + UpdateUI(); + UpdateInputDeviceCombobox(); } void ConfigureInput::RestoreDefaults() { - 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])}; - SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); - } - analogs_param[analog_id].Set("modifier_scale", 0.5f); - } - UpdateButtonLabels(); - - ApplyConfiguration(); - Settings::SaveProfile(Settings::values.current_input_profile_index); + UpdateMappingWithDefaults(); } void ConfigureInput::ClearAll() { - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - if (button_map[button_id] && button_map[button_id]->isEnabled()) - buttons_param[button_id].Clear(); - } - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - analogs_param[analog_id].Clear(); - } - UpdateButtonLabels(); - - ApplyConfiguration(); - Settings::SaveProfile(Settings::values.current_input_profile_index); -} - -void ConfigureInput::UpdateButtonLabels() { - for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { - if (button_map[button]) - button_map[button]->setText(ButtonToText(buttons_param[button])); + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { + const auto* const button = button_map[button_id]; + if (button == nullptr) { + continue; + } + emulated_controller->SetButtonParam(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++) { - if (analog_map_buttons[analog_id][sub_button_id]) { - analog_map_buttons[analog_id][sub_button_id]->setText( - AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_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) { + const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; + if (analog_button == nullptr) { + continue; } + emulated_controller->SetStickParam(analog_id, {}); } - analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); + } - auto& param = analogs_param[analog_id]; - auto* const analog_stick_slider = analog_map_deadzone_and_modifier_slider[analog_id]; - auto* const analog_stick_slider_label = - analog_map_deadzone_and_modifier_slider_label[analog_id]; + UpdateUI(); + UpdateInputDevices(); +} - if (param.Has("engine")) { - const auto engine{param.Get("engine", "")}; - if (engine == "sdl" || engine == "gcpad") { - if (!param.Has("deadzone")) { - param.Set("deadzone", 0.1f); - } - const auto slider_value = static_cast(param.Get("deadzone", 0.1f) * 100); - analog_stick_slider_label->setText(tr("Deadzone: %1%").arg(slider_value)); - analog_stick_slider->setValue(slider_value); - } else { - if (!param.Has("modifier_scale")) { - param.Set("modifier_scale", 0.5f); - } - const auto slider_value = static_cast(param.Get("modifier_scale", 0.5f) * 100); - analog_stick_slider_label->setText(tr("Modifier Scale: %1%").arg(slider_value)); - analog_stick_slider->setValue(slider_value); +void ConfigureInput::UpdateUI() { + for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) { + const Common::ParamPackage param = emulated_controller->GetButtonParam(button); + button_map[button]->setText(ButtonToText(param)); + } + + const Common::ParamPackage ZL_param = + emulated_controller->GetButtonParam(Settings::NativeButton::ZL); + if (ZL_param.Has("threshold")) { + const int button_threshold = static_cast(ZL_param.Get("threshold", 0.5f) * 100.0f); + ui->sliderZLThreshold->setValue(button_threshold); + } + + const Common::ParamPackage ZR_param = + emulated_controller->GetButtonParam(Settings::NativeButton::ZR); + if (ZR_param.Has("threshold")) { + const int button_threshold = static_cast(ZR_param.Get("threshold", 0.5f) * 100.0f); + ui->sliderZRThreshold->setValue(button_threshold); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { + const Common::ParamPackage param = emulated_controller->GetStickParam(analog_id); + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; + + if (analog_button == nullptr) { + continue; } - } - } - ui->buttonCircleMod->setText(AnalogToText(analogs_param[0], "modifier")); - - EmitInputKeysChanged(); -} - -void ConfigureInput::MapFromButton(const Common::ParamPackage& params) { - Common::ParamPackage aux_param; - bool mapped = false; - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - aux_param = InputCommon::GetControllerButtonBinds(params, button_id); - if (aux_param.Has("engine")) { - buttons_param[button_id] = aux_param; - mapped = true; + analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id])); } - } - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - aux_param = InputCommon::GetControllerAnalogBinds(params, analog_id); - if (aux_param.Has("engine")) { - analogs_param[analog_id] = aux_param; - mapped = true; + + analog_map_modifier_button[analog_id]->setText( + ButtonToText(Common::ParamPackage{param.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]; + const auto modifier_label = analog_map_modifier_label[analog_id]; + const auto modifier_slider = analog_map_modifier_slider[analog_id]; + const auto range_groupbox = analog_map_range_groupbox[analog_id]; + const auto range_spinbox = analog_map_range_spinbox[analog_id]; + + int slider_value; + const bool is_controller = input_subsystem->IsController(param); + + if (is_controller) { + slider_value = static_cast(param.Get("deadzone", 0.15f) * 100); + deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value)); + deadzone_slider->setValue(slider_value); + range_spinbox->setValue(static_cast(param.Get("range", 0.95f) * 100)); + } else { + slider_value = static_cast(param.Get("modifier_scale", 0.5f) * 100); + modifier_label->setText(tr("Modifier Range: %1%").arg(slider_value)); + modifier_slider->setValue(slider_value); } - } - if (!mapped) { - QMessageBox::warning( - this, tr("Warning"), - tr("Auto mapping failed. Your controller may not have a corresponding mapping")); + + deadzone_label->setVisible(is_controller); + deadzone_slider->setVisible(is_controller); + modifier_groupbox->setVisible(!is_controller); + modifier_label->setVisible(!is_controller); + modifier_slider->setVisible(!is_controller); + range_groupbox->setVisible(is_controller); } } -void ConfigureInput::AutoMap() { - if (QMessageBox::information(this, tr("Information"), - tr("After pressing OK, press any button on your joystick"), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { +void ConfigureInput::UpdateMappingWithDefaults() { + if (ui->comboDevices->currentIndex() == 0) { return; } - input_setter = [this](const Common::ParamPackage& params) { - MapFromButton(params); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); - }; - device_pollers = InputCommon::Polling::GetPollers(InputCommon::Polling::DeviceType::Button); - want_keyboard_keys = false; - for (auto& poller : device_pollers) { - poller->Start(); + + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { + const auto* const button = button_map[button_id]; + if (button == nullptr) { + continue; + } + emulated_controller->SetButtonParam(button_id, {}); } - timeout_timer->start(5000); // Cancel after 5 seconds - poll_timer->start(200); // Check for new inputs every 200ms + + 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) { + continue; + } + emulated_controller->SetStickParam(analog_id, {}); + } + } + + // Reset keyboard or mouse bindings + if (ui->comboDevices->currentIndex() == 1 || ui->comboDevices->currentIndex() == 2) { + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { + emulated_controller->SetButtonParam( + button_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam( + Config::default_buttons[button_id])}); + } + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { + Common::ParamPackage analog_param{}; + 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, analog_param, analog_sub_buttons[sub_button_id]); + } + + /* analog_param.Set("modifier", InputCommon::GenerateKeyboardParam( + Config::default_stick_mod[analog_id]));*/ + emulated_controller->SetStickParam(analog_id, analog_param); + } + + // If mouse is selected we want to override with mappings from the driver + if (ui->comboDevices->currentIndex() == 1) { + UpdateUI(); + return; + } + } + + // Reset controller bindings + const auto& device = input_devices[ui->comboDevices->currentIndex()]; + auto button_mappings = input_subsystem->GetButtonMappingForDevice(device); + auto analog_mappings = input_subsystem->GetAnalogMappingForDevice(device); + auto motion_mappings = input_subsystem->GetMotionMappingForDevice(device); + + for (const auto& button_mapping : button_mappings) { + const std::size_t index = button_mapping.first; + emulated_controller->SetButtonParam(index, button_mapping.second); + } + for (const auto& analog_mapping : analog_mappings) { + const std::size_t index = analog_mapping.first; + emulated_controller->SetStickParam(index, analog_mapping.second); + } + for (const auto& motion_mapping : motion_mappings) { + const std::size_t index = motion_mapping.first; + emulated_controller->SetMotionParam(index, motion_mapping.second); + } + + UpdateUI(); } -void ConfigureInput::HandleClick(QPushButton* button, +void ConfigureInput::HandleClick(QPushButton* button, std::size_t button_id, std::function new_input_setter, - InputCommon::Polling::DeviceType type) { - previous_key_code = QKeySequence(button->text())[0].toCombined(); - button->setText(tr("[press key]")); + InputCommon::Polling::InputType type) { + if (timeout_timer->isActive()) { + return; + } button->setFocus(); - input_setter = new_input_setter; + input_setter = std::move(new_input_setter); - device_pollers = InputCommon::Polling::GetPollers(type); + input_subsystem->BeginMapping(type); - // Keyboard keys can only be used as button devices - want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; + QWidget::grabMouse(); + QWidget::grabKeyboard(); - for (auto& poller : device_pollers) { - poller->Start(); + timeout_timer->start(2500); // Cancel after 2.5 seconds + poll_timer->start(25); // Check for new inputs every 25ms +} + +bool ConfigureInput::IsInputAcceptable(const Common::ParamPackage& params) const { + if (ui->comboDevices->currentIndex() == 0) { + return true; } - grabKeyboard(); - grabMouse(); - timeout_timer->start(5000); // Cancel after 5 seconds - poll_timer->start(200); // Check for new inputs every 200ms + if (params.Has("motion")) { + return true; + } + + // Keyboard/Mouse + if (ui->comboDevices->currentIndex() == 1 || ui->comboDevices->currentIndex() == 2) { + 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("engine", "") && + (params.Get("guid", "") == current_input_device.Get("guid", "") || + params.Get("guid", "") == current_input_device.Get("guid2", "")) && + params.Get("port", 0) == current_input_device.Get("port", 0); } void ConfigureInput::SetPollingResult(const Common::ParamPackage& params, bool abort) { - releaseKeyboard(); - releaseMouse(); timeout_timer->stop(); poll_timer->stop(); - for (auto& poller : device_pollers) { - poller->Stop(); - } + input_subsystem->StopMapping(); - if (!abort && input_setter) { + QWidget::releaseMouse(); + QWidget::releaseKeyboard(); + + if (!abort) { (*input_setter)(params); } - UpdateButtonLabels(); - input_setter.reset(); + UpdateUI(); + UpdateInputDeviceCombobox(); + + input_setter = std::nullopt; } void ConfigureInput::keyPressEvent(QKeyEvent* event) { @@ -645,6 +941,91 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) { void ConfigureInput::RetranslateUI() { ui->retranslateUi(this); + UpdateUI(); +} + +void ConfigureInput::UpdateInputDevices() { + input_devices = input_subsystem->GetInputDevices(); + ui->comboDevices->clear(); + for (const auto& device : input_devices) { + ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); + } +} + +void ConfigureInput::UpdateInputDeviceCombobox() { + // Skip input device persistence if "Input Devices" is set to "Any". + if (ui->comboDevices->currentIndex() == 0) { + UpdateInputDevices(); + return; + } + + const auto devices = emulated_controller->GetMappedDevices(); + UpdateInputDevices(); + + if (devices.empty()) { + return; + } + + if (devices.size() > 2) { + ui->comboDevices->setCurrentIndex(0); + return; + } + + const auto first_engine = devices[0].Get("engine", ""); + const auto first_guid = devices[0].Get("guid", ""); + const auto first_port = devices[0].Get("port", 0); + const auto first_pad = devices[0].Get("pad", 0); + + if (devices.size() == 1) { + const auto devices_it = std::find_if( + input_devices.begin(), input_devices.end(), + [first_engine, first_guid, first_port, first_pad](const Common::ParamPackage& param) { + return param.Get("engine", "") == first_engine && + param.Get("guid", "") == first_guid && param.Get("port", 0) == first_port && + param.Get("pad", 0) == first_pad; + }); + const int device_index = + devices_it != input_devices.end() + ? static_cast(std::distance(input_devices.begin(), devices_it)) + : 0; + ui->comboDevices->setCurrentIndex(device_index); + return; + } + + const auto second_engine = devices[1].Get("engine", ""); + const auto second_guid = devices[1].Get("guid", ""); + const auto second_port = devices[1].Get("port", 0); + + const bool is_keyboard_mouse = (first_engine == "keyboard" || first_engine == "mouse") && + (second_engine == "keyboard" || second_engine == "mouse"); + + if (is_keyboard_mouse) { + ui->comboDevices->setCurrentIndex(2); + return; + } + + const bool is_engine_equal = first_engine == second_engine; + const bool is_port_equal = first_port == second_port; + + if (is_engine_equal && is_port_equal) { + const auto devices_it = std::find_if( + input_devices.begin(), input_devices.end(), + [first_engine, first_guid, second_guid, first_port](const Common::ParamPackage& param) { + const bool is_guid_valid = + (param.Get("guid", "") == first_guid && + param.Get("guid2", "") == second_guid) || + (param.Get("guid", "") == second_guid && param.Get("guid2", "") == first_guid); + return param.Get("engine", "") == first_engine && is_guid_valid && + param.Get("port", 0) == first_port; + }); + const int device_index = + devices_it != input_devices.end() + ? static_cast(std::distance(input_devices.begin(), devices_it)) + : 0; + ui->comboDevices->setCurrentIndex(device_index); + } else { + ui->comboDevices->setCurrentIndex(0); + } } void ConfigureInput::NewProfile() { @@ -664,7 +1045,7 @@ void ConfigureInput::NewProfile() { ui->profile->addItem(name); ui->profile->setCurrentIndex(Settings::values.current_input_profile_index); LoadConfiguration(); - ui->buttonDelete->setEnabled(ui->profile->count() > 1); + ui->buttonProfilesDelete->setEnabled(ui->profile->count() > 1); } void ConfigureInput::DeleteProfile() { @@ -678,7 +1059,7 @@ void ConfigureInput::DeleteProfile() { ui->profile->setCurrentIndex(0); Settings::DeleteProfile(index); LoadConfiguration(); - ui->buttonDelete->setEnabled(ui->profile->count() > 1); + ui->buttonProfilesDelete->setEnabled(ui->profile->count() > 1); } void ConfigureInput::RenameProfile() { diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h index fb00444c6..810bb73d3 100644 --- a/src/citra_qt/configuration/configure_input.h +++ b/src/citra_qt/configuration/configure_input.h @@ -9,7 +9,10 @@ #include #include #include +#include +#include #include +#include #include #include "common/param_package.h" #include "common/settings.h" @@ -22,6 +25,10 @@ class QSlider; class QString; class QTimer; +namespace Core::HID { +class EmulatedController; +} // namespace Core::HID + namespace Ui { class ConfigureInput; } @@ -33,6 +40,8 @@ public: explicit ConfigureInput(QWidget* parent = nullptr); ~ConfigureInput() override; + void Initialize(InputCommon::InputSubsystem* input_subsystem_); + /// Save all button configurations to settings file void ApplyConfiguration(); void RetranslateUI(); @@ -50,14 +59,24 @@ signals: void InputKeysChanged(QList new_key_list); private: + QString ButtonToText(const Common::ParamPackage& param); + QString AnalogToText(const Common::ParamPackage& param, const std::string& dir); + + /// Update UI to reflect current configuration. + void UpdateUI(); + std::unique_ptr ui; + InputCommon::InputSubsystem* input_subsystem; + std::unique_ptr timeout_timer; std::unique_ptr poll_timer; /// This will be the the setting function when an input is awaiting configuration. std::optional> input_setter; + Core::HID::EmulatedController* emulated_controller; + std::array buttons_param; std::array analogs_param; @@ -74,14 +93,19 @@ private: /// Analog inputs are also represented each with a single button, used to configure with an /// actual analog stick std::array analog_map_stick; - std::array - analog_map_deadzone_and_modifier_slider; - std::array - analog_map_deadzone_and_modifier_slider_label; - static const std::array analog_sub_buttons; - std::vector> device_pollers; + std::array analog_map_deadzone_label; + std::array analog_map_deadzone_slider; + std::array analog_map_modifier_groupbox; + std::array analog_map_modifier_button; + std::array analog_map_modifier_label; + std::array analog_map_modifier_slider; + std::array analog_map_range_groupbox; + std::array analog_map_range_spinbox; + + /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once. + bool map_analog_stick_accepted{}; /** * List of keys currently registered to hotkeys. @@ -94,34 +118,39 @@ private: /// keyboard events are ignored. bool want_keyboard_keys = false; + std::vector input_devices; + /// Generates list of all used keys QList GetUsedKeyboardKeys(); - void MapFromButton(const Common::ParamPackage& params); - void AutoMap(); - /// Restore all buttons to their default values. void RestoreDefaults(); /// Clear all input configuration void ClearAll(); - /// Update UI to reflect current configuration. - void UpdateButtonLabels(); + /// Gets the default controller mapping for this device and auto configures the input to match. + void UpdateMappingWithDefaults(); /// Called when the button was pressed. - void HandleClick(QPushButton* button, + void HandleClick(QPushButton* button, std::size_t button_id, std::function new_input_setter, - InputCommon::Polling::DeviceType type); + InputCommon::Polling::InputType type); /// The key code of the previous state of the key being currently bound. int previous_key_code; + /// Checks whether a given input can be accepted. + bool IsInputAcceptable(const Common::ParamPackage& params) const; + /// Finish polling and configure input using the input_setter void SetPollingResult(const Common::ParamPackage& params, bool abort); /// Handle key press events. void keyPressEvent(QKeyEvent* event) override; + void UpdateInputDevices(); + void UpdateInputDeviceCombobox(); + /// input profiles void NewProfile(); void DeleteProfile(); diff --git a/src/citra_qt/configuration/configure_input.ui b/src/citra_qt/configuration/configure_input.ui index 2afae408e..2541f767d 100644 --- a/src/citra_qt/configuration/configure_input.ui +++ b/src/citra_qt/configuration/configure_input.ui @@ -6,8 +6,8 @@ 0 0 - 441 - 727 + 554 + 869 @@ -15,49 +15,83 @@ - + + + 3 + - - + + + Input Device + + + + 3 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + + + Profile - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - New - - - - - - - Delete - - - - - - - Rename - + + + 6 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + + New + + + + + + + Rename + + + + + + + Delete + + + + @@ -66,180 +100,678 @@ + + + 0 + 0 + + Face Buttons + + Qt::AlignCenter + false false - - - - - - - Y: + + + 0 + + + 3 + + + 6 + + + 3 + + + 3 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + X + + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + X + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + + + 3 + + + + + Y + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Y + + + + - - - - + + + + A + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + A + + + + - - - - - - X: - - - - - - - - - - - - - - - - - - B: - - - - - - - - - - - - - - - - - - A: - - - - - - - - - - - + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + B + + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + B + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + - + + + + 0 + 0 + + Directional Pad + + Qt::AlignCenter + false false - - - - - - - Up: + + + 0 + + + 3 + + + 6 + + + 3 + + + 3 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Up + + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Up + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + + + 3 + + + + + Left + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Left + + + + - - - - + + + + Right + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Right + + + + - - - - - - Down: - - - - - - - - - - - - - - - - - - Left: - - - - - - - - - - - - - - - - - - Right: - - - - - - - - - - - + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Down + + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Down + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + - + Misc. @@ -250,7 +782,7 @@ false - + @@ -268,7 +800,7 @@ - + @@ -286,7 +818,7 @@ - + @@ -304,25 +836,7 @@ - - - - - - Circle Mod: - - - - - - - - - - - - - + @@ -340,7 +854,25 @@ - + + + + + + Circle Mod: + + + + + + + + + + + + + @@ -362,107 +894,472 @@ - + + + + 0 + 0 + + Circle Pad - - false + + Qt::AlignHCenter|Qt::AlignTop - - false - - - - - - - - Up: - - - - - - - - - - - - - - - - - - Right: - - - - - - - - - - - - - - - - - - Left: - - - - - - - - - - - - - - - - Set Analog Stick - + + + 0 + + + QLayout::SetDefaultConstraint + + + 3 + + + 6 + + + 3 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Up + + + Qt::AlignCenter + + + false + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Up + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + - - - - - - Down: + + + + 3 + + + + + Left + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Left + + + + - - - - + + + + Right + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Right + + + + - - - - - - QLayout::SetDefaultConstraint + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Down + + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Down + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + + + 3 + + + + + Modifier + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Modifier + + + + + + + + + + Range + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 21 + + + + + 68 + 16777215 + + + + % + + + 25 + + + 150 + + + 95 + + + + + + + + + + + + 3 + + + QLayout::SetDefaultConstraint + + + 0 + + + 2 + + + 0 + + + 3 + + + - + - Deadzone: 0 + Deadzone: 0% Qt::AlignHCenter @@ -472,7 +1369,34 @@ - + + + 100 + + + Qt::Horizontal + + + + + + + + + Modifier Range: 0% + + + Qt::AlignHCenter + + + + + + + + + 100 + Qt::Horizontal @@ -484,107 +1408,475 @@ - + + + + 0 + 0 + + C-Stick + + Qt::AlignCenter + false false - - - - - - - Right: - - - - - - - - - - - - - - - - - - Left: - - - - - - - - - - - - - - - - - - Up: - - - - - - - - - - - - - - - - Set Analog Stick + + + 0 + + + 3 + + + 6 + + + 3 + + + 0 + + + + + + 0 + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Up + + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Up + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + - - - - - - Down: + + + + 3 + + + + + Left + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Left + + + + - - - - + + + + Right + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Right + + + + - - - - QLayout::SetDefaultConstraint + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Down + + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Down + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + + + 3 + + + + + Modifier + + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Modifier + + + + + + + + + + Range + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 21 + + + + + 68 + 16777215 + + + + % + + + 25 + + + 150 + + + 95 + + + + + + + + + + + + 3 + + + 0 + + + 2 + + + 0 + + + 3 - + - + - Deadzone: 0 + Deadzone: 0% Qt::AlignHCenter @@ -594,7 +1886,34 @@ - + + + 100 + + + Qt::Horizontal + + + + + + + + + Modifier Range: 0% + + + Qt::AlignHCenter + + + + + + + + + 100 + Qt::Horizontal @@ -606,7 +1925,7 @@ - + Shoulder Buttons @@ -617,7 +1936,7 @@ false - + @@ -633,9 +1952,16 @@ + + + + Qt::Horizontal + + + - + @@ -651,9 +1977,16 @@ + + + + Qt::Horizontal + + + - + @@ -671,7 +2004,7 @@ - + @@ -726,34 +2059,6 @@ - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 0 - 0 - - - - Qt::LeftToRight - - - Auto Map - - - @@ -817,10 +2122,6 @@ - profile - buttonNew - buttonDelete - buttonRename buttonA buttonB buttonX @@ -829,18 +2130,6 @@ buttonDpadRight buttonDpadUp buttonDpadDown - buttonCircleLeft - buttonCircleRight - buttonCircleUp - buttonCircleDown - buttonCircleAnalog - sliderCirclePadDeadzoneAndModifier - buttonCStickLeft - buttonCStickRight - buttonCStickUp - buttonCStickDown - buttonCStickAnalog - sliderCStickDeadzoneAndModifier buttonL buttonR buttonZL @@ -852,7 +2141,6 @@ buttonDebug buttonGpio14 buttonMotionTouch - buttonAutoMap buttonClearAll buttonRestoreDefaults diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 3fb9d22af..2edddd48b 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -12,6 +12,9 @@ #include "common/logging/log.h" #include "core/3ds.h" #include "core/core.h" +#include "core/hid/emulated_console.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" @@ -43,9 +46,6 @@ void Module::serialize(Archive& ar, const unsigned int file_version) { ar& next_gyroscope_index; ar& enable_accelerometer_count; ar& enable_gyroscope_count; - if (Archive::is_loading::value) { - LoadInputDevices(); - } if (file_version >= 1) { ar& state.hex; } @@ -86,65 +86,23 @@ DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { return state; } -void Module::LoadInputDevices() { - std::transform(Settings::values.current_input_profile.buttons.begin() + - Settings::NativeButton::BUTTON_HID_BEGIN, - Settings::values.current_input_profile.buttons.begin() + - Settings::NativeButton::BUTTON_HID_END, - buttons.begin(), Input::CreateDevice); - circle_pad = Input::CreateDevice( - Settings::values.current_input_profile.analogs[Settings::NativeAnalog::CirclePad]); - motion_device = Input::CreateDevice( - Settings::values.current_input_profile.motion_device); - touch_device = Input::CreateDevice( - Settings::values.current_input_profile.touch_device); - if (Settings::values.current_input_profile.use_touch_from_button) { - touch_btn_device = Input::CreateDevice("engine:touch_from_button"); - } else { - touch_btn_device.reset(); - } -} - void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { SharedMem* mem = reinterpret_cast(shared_mem->GetPointer()); - if (is_device_reload_pending.exchange(false)) - LoadInputDevices(); - - using namespace Settings::NativeButton; - state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); - state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); - state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); - state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); - state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus()); - state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus()); - state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus()); - state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus()); - state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); - state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); - state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus()); - state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus()); - state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus()); - state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus()); + const auto& controller = hid_core.GetEmulatedController(); + state = controller->GetPadState(); // Get current circle pad position and update circle pad direction - float circle_pad_x_f, circle_pad_y_f; - std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); + const s16 circle_pad_new_x = controller->GetSticks().circle_pad.x; + const s16 circle_pad_new_y = controller->GetSticks().circle_pad.y; - // xperia64: 0x9A seems to be the calibrated limit of the circle pad - // Verified by using Input Redirector with very large-value digital inputs - // on the circle pad and calibrating using the system settings application - constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position - - // These are rounded rather than truncated on actual hardware - s16 circle_pad_new_x = static_cast(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS)); - s16 circle_pad_new_y = static_cast(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS)); s16 circle_pad_x = (circle_pad_new_x + std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) / CIRCLE_PAD_AVERAGING; s16 circle_pad_y = (circle_pad_new_y + std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) / CIRCLE_PAD_AVERAGING; + circle_pad_old_x.erase(circle_pad_old_x.begin()); circle_pad_old_x.push_back(circle_pad_new_x); circle_pad_old_y.erase(circle_pad_old_y.begin()); @@ -190,12 +148,10 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { // Get the current touch entry TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; - bool pressed = false; - float x, y; - std::tie(x, y, pressed) = touch_device->GetStatus(); - if (!pressed && touch_btn_device) { - std::tie(x, y, pressed) = touch_btn_device->GetStatus(); - } + const auto& touch_state = hid_core.GetEmulatedConsole()->GetTouch()[0]; + float x = touch_state.position_x; + float y = touch_state.position_y; + bool pressed = touch_state.pressed; touch_entry.x = static_cast(x * Core::kScreenBottomWidth); touch_entry.y = static_cast(y * Core::kScreenBottomHeight); touch_entry.valid.Assign(pressed ? 1 : 0); @@ -232,8 +188,8 @@ void Module::UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_la mem->accelerometer.index = next_accelerometer_index; next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); - Common::Vec3 accel; - std::tie(accel, std::ignore) = motion_device->GetStatus(); + const auto& motion_state = hid_core.GetEmulatedConsole()->GetMotion(); + Common::Vec3 accel = motion_state.gyro; accel *= accelerometer_coef; // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback // The time stretch formula should be like @@ -279,8 +235,8 @@ void Module::UpdateGyroscopeCallback(std::uintptr_t user_data, s64 cycles_late) GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; - Common::Vec3 gyro; - std::tie(std::ignore, gyro) = motion_device->GetStatus(); + const auto& motion_state = hid_core.GetEmulatedConsole()->GetMotion(); + Common::Vec3 gyro = motion_state.gyro; double stretch = system.perf_stats->GetLastFrameTimeScale(); gyro *= gyroscope_coef * static_cast(stretch); gyroscope_entry.x = static_cast(gyro.x); @@ -421,7 +377,7 @@ std::shared_ptr Module::Interface::GetModule() const { return hid; } -Module::Module(Core::System& system) : system(system) { +Module::Module(Core::System& system) : system(system), hid_core{system.HIDCore()} { using namespace Kernel; shared_mem = @@ -455,10 +411,6 @@ Module::Module(Core::System& system) : system(system) { timing.ScheduleEvent(pad_update_ticks, pad_update_event); } -void Module::ReloadInputDevices() { - is_device_reload_pending.store(true); -} - const PadState& Module::GetState() const { return state; } diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 609cb9276..18bd4a8a3 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -13,9 +13,10 @@ #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "common/input.h" #include "common/settings.h" #include "core/core_timing.h" -#include "core/frontend/input.h" +#include "core/hid/hid_types.h" #include "core/hle/service/service.h" namespace Core { @@ -31,36 +32,13 @@ namespace Core { struct TimingEventType; }; +namespace Core::HID { +class HIDCore; +}; + namespace Service::HID { -/** - * Structure of a Pad controller state. - */ -struct PadState { - union { - u32 hex{}; - - BitField<0, 1, u32> a; - BitField<1, 1, u32> b; - BitField<2, 1, u32> select; - BitField<3, 1, u32> start; - BitField<4, 1, u32> right; - BitField<5, 1, u32> left; - BitField<6, 1, u32> up; - BitField<7, 1, u32> down; - BitField<8, 1, u32> r; - BitField<9, 1, u32> l; - BitField<10, 1, u32> x; - BitField<11, 1, u32> y; - BitField<12, 1, u32> debug; - BitField<13, 1, u32> gpio14; - - BitField<28, 1, u32> circle_right; - BitField<29, 1, u32> circle_left; - BitField<30, 1, u32> circle_up; - BitField<31, 1, u32> circle_down; - }; -}; +using Core::HID::PadState; /** * Structure of a single entry of Pad state history within HID shared memory @@ -296,8 +274,6 @@ public: std::shared_ptr hid; }; - void ReloadInputDevices(); - const PadState& GetState() const; // Updating period for each HID device. These empirical values are measured from a 11.2 3DS. @@ -306,7 +282,6 @@ public: static constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; private: - void LoadInputDevices(); void UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late); void UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_late); void UpdateGyroscopeCallback(std::uintptr_t user_data, s64 cycles_late); @@ -347,13 +322,7 @@ private: Core::TimingEventType* accelerometer_update_event; Core::TimingEventType* gyroscope_update_event; - std::atomic is_device_reload_pending{true}; - std::array, Settings::NativeButton::NUM_BUTTONS_HID> - buttons; - std::unique_ptr circle_pad; - std::unique_ptr motion_device; - std::unique_ptr touch_device; - std::unique_ptr touch_btn_device; + Core::HID::HIDCore& hid_core; template void serialize(Archive& ar, const unsigned int); diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp index 5e7511033..1102e5c98 100644 --- a/src/core/hle/service/ir/extra_hid.cpp +++ b/src/core/hle/service/ir/extra_hid.cpp @@ -7,6 +7,8 @@ #include "common/string_util.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" #include "core/hle/service/ir/extra_hid.h" #include "core/movie.h" @@ -64,9 +66,8 @@ enum class ResponseID : u8 { ReadCalibrationData = 0x11, }; -ExtraHID::ExtraHID(SendFunc send_func, Core::Timing& timing) : IRDevice(send_func), timing(timing) { - LoadInputDevices(); - +ExtraHID::ExtraHID(SendFunc send_func, Core::System& system) + : IRDevice(send_func), timing(system.CoreTiming()), hid_core{system.HIDCore()} { // The data below was retrieved from a New 3DS // TODO(wwylele): this data is probably writable (via request 3?) and thus should be saved to // and loaded from somewhere. @@ -228,24 +229,23 @@ void ExtraHID::OnReceive(const std::vector& data) { } void ExtraHID::SendHIDStatus() { - if (is_device_reload_pending.exchange(false)) - LoadInputDevices(); - constexpr int C_STICK_CENTER = 0x800; // TODO(wwylele): this value is not accurately measured. We currently assume that the axis can // take values in the whole range of a 12-bit integer. constexpr int C_STICK_RADIUS = 0x7FF; - float x, y; - std::tie(x, y) = c_stick->GetStatus(); + // TODO: Correct this!!!!! + const auto& controller = hid_core.GetEmulatedController(); + float x = controller->GetSticks().c_stick.x; + float y = controller->GetSticks().c_stick.y; ExtraHIDResponse response; response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); response.c_stick.c_stick_x.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * x)); response.c_stick.c_stick_y.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * y)); response.buttons.battery_level.Assign(0x1F); - response.buttons.zl_not_held.Assign(!zl->GetStatus()); - response.buttons.zr_not_held.Assign(!zr->GetStatus()); + response.buttons.zl_not_held.Assign(!controller->GetExtraState().zl); + response.buttons.zr_not_held.Assign(!controller->GetExtraState().zr); response.buttons.r_not_held.Assign(1); response.unknown = 0; @@ -256,17 +256,4 @@ void ExtraHID::SendHIDStatus() { Send(response_buffer); } -void ExtraHID::RequestInputDevicesReload() { - is_device_reload_pending.store(true); -} - -void ExtraHID::LoadInputDevices() { - zl = Input::CreateDevice( - Settings::values.current_input_profile.buttons[Settings::NativeButton::ZL]); - zr = Input::CreateDevice( - Settings::values.current_input_profile.buttons[Settings::NativeButton::ZR]); - c_stick = Input::CreateDevice( - Settings::values.current_input_profile.analogs[Settings::NativeAnalog::CStick]); -} - } // namespace Service::IR diff --git a/src/core/hle/service/ir/extra_hid.h b/src/core/hle/service/ir/extra_hid.h index 5899f2b47..f5ccbed94 100644 --- a/src/core/hle/service/ir/extra_hid.h +++ b/src/core/hle/service/ir/extra_hid.h @@ -17,6 +17,10 @@ struct TimingEventType; class Timing; } // namespace Core +namespace Core::HID { +class HIDCore; +}; + namespace Service::IR { struct ExtraHIDResponse { @@ -42,7 +46,7 @@ static_assert(sizeof(ExtraHIDResponse) == 6, "HID status response has wrong size */ class ExtraHID final : public IRDevice { public: - explicit ExtraHID(SendFunc send_func, Core::Timing& timing); + explicit ExtraHID(SendFunc send_func, Core::System& system); ~ExtraHID(); void OnConnect() override; @@ -58,6 +62,7 @@ private: u8 hid_period; Core::TimingEventType* hid_polling_callback_id; std::array calibration_data; + Core::HID::HIDCore& hid_core; template void serialize(Archive& ar, const unsigned int) { diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 0e4d1b920..063638343 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -8,6 +8,7 @@ #include "common/settings.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/hid/emulated_controller.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" @@ -29,7 +30,6 @@ void IR_RST::serialize(Archive& ar, const unsigned int) { ar& raw_c_stick; ar& update_period; // update_callback_id and input devices are set separately - ReloadInputDevices(); } struct PadDataEntry { @@ -51,34 +51,19 @@ struct SharedMem { static_assert(sizeof(SharedMem) == 0x98, "SharedMem has wrong size!"); -void IR_RST::LoadInputDevices() { - zl_button = Input::CreateDevice( - Settings::values.current_input_profile.buttons[Settings::NativeButton::ZL]); - zr_button = Input::CreateDevice( - Settings::values.current_input_profile.buttons[Settings::NativeButton::ZR]); - c_stick = Input::CreateDevice( - Settings::values.current_input_profile.analogs[Settings::NativeAnalog::CStick]); -} - -void IR_RST::UnloadInputDevices() { - zl_button = nullptr; - zr_button = nullptr; - c_stick = nullptr; -} - void IR_RST::UpdateCallback(std::uintptr_t user_data, s64 cycles_late) { SharedMem* mem = reinterpret_cast(shared_memory->GetPointer()); - if (is_device_reload_pending.exchange(false)) - LoadInputDevices(); - + const auto& controller = hid_core.GetEmulatedController(); PadState state; - state.zl.Assign(zl_button->GetStatus()); - state.zr.Assign(zr_button->GetStatus()); + state.zl.Assign(controller->GetExtraState().zl); + state.zr.Assign(controller->GetExtraState().zr); // Get current c-stick position and update c-stick direction - float c_stick_x_f, c_stick_y_f; - std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); + // TODO: FIX!!!! (same as in extra_hid) + float c_stick_x_f = controller->GetSticks().c_stick.x; + float c_stick_y_f = controller->GetSticks().c_stick.y; + constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius s16 c_stick_x = static_cast(c_stick_x_f * MAX_CSTICK_RADIUS); s16 c_stick_y = static_cast(c_stick_y_f * MAX_CSTICK_RADIUS); @@ -143,7 +128,6 @@ void IR_RST::Initialize(Kernel::HLERequestContext& ctx) { LOG_ERROR(Service_IR, "raw C-stick data is not implemented!"); next_pad_index = 0; - is_device_reload_pending.store(true); system.CoreTiming().ScheduleEvent(msToCycles(update_period), update_callback_id); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); @@ -156,14 +140,14 @@ void IR_RST::Shutdown(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x03, 0, 0); system.CoreTiming().UnscheduleEvent(update_callback_id, 0); - UnloadInputDevices(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_IR, "called"); } -IR_RST::IR_RST(Core::System& system) : ServiceFramework("ir:rst", 1), system(system) { +IR_RST::IR_RST(Core::System& system) + : ServiceFramework("ir:rst", 1), system(system), hid_core{system.HIDCore()} { using namespace Kernel; // Note: these two kernel objects are even available before Initialize service function is // called. @@ -192,8 +176,4 @@ IR_RST::IR_RST(Core::System& system) : ServiceFramework("ir:rst", 1), system(sys IR_RST::~IR_RST() = default; -void IR_RST::ReloadInputDevices() { - is_device_reload_pending.store(true); -} - } // namespace Service::IR diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h index 42b0613e6..18768c5a7 100644 --- a/src/core/hle/service/ir/ir_rst.h +++ b/src/core/hle/service/ir/ir_rst.h @@ -21,6 +21,10 @@ namespace Core { struct TimingEventType; }; +namespace Core::HID { +class HIDCore; +}; + namespace Service::IR { union PadState { @@ -80,6 +84,7 @@ private: Core::TimingEventType* update_callback_id; bool raw_c_stick{false}; int update_period{0}; + Core::HID::HIDCore& hid_core; template void serialize(Archive& ar, const unsigned int); diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index e0fa3a73c..402e912b8 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -464,7 +464,7 @@ IR_USER::IR_USER(Core::System& system) : ServiceFramework("ir:USER", 1) { receive_event = system.Kernel().CreateEvent(ResetType::OneShot, "IR:ReceiveEvent"); extra_hid = std::make_unique( - [this](const std::vector& data) { PutToReceive(data); }, system.CoreTiming()); + [this](const std::vector& data) { PutToReceive(data); }, system); } IR_USER::~IR_USER() { diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp index 8f6afe3fb..bdd302328 100644 --- a/src/input_common/drivers/udp_client.cpp +++ b/src/input_common/drivers/udp_client.cpp @@ -151,32 +151,17 @@ UDPClient::ClientConnection::~ClientConnection() = default; void UDPClient::ReloadSockets() { Reset(); - std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue()); - std::string server_token; - std::size_t client = 0; - while (std::getline(servers_ss, server_token, ',')) { - if (client == MAX_UDP_CLIENTS) { - break; - } - std::stringstream server_ss(server_token); - std::string token; - std::getline(server_ss, token, ':'); - std::string udp_input_address = token; - std::getline(server_ss, token, ':'); - char* temp; - const u16 udp_input_port = static_cast(std::strtol(token.c_str(), &temp, 0)); - if (*temp != '\0') { - LOG_ERROR(Input, "Port number is not valid {}", token); - continue; - } + const std::string& host = Settings::values.current_input_profile.udp_input_address; + const u16 udp_input_port = Settings::values.current_input_profile.udp_input_port; + // TODO: Hook it up again + const u8 pad_index = Settings::values.current_input_profile.udp_pad_index; - const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port); - if (client_number != MAX_UDP_CLIENTS) { - LOG_ERROR(Input, "Duplicated UDP servers found"); - continue; - } - StartCommunication(client++, udp_input_address, udp_input_port); + const std::size_t client_number = GetClientNumber(host, udp_input_port); + if (client_number != MAX_UDP_CLIENTS) { + LOG_ERROR(Input, "Duplicated UDP servers found"); + return; } + StartCommunication(0, host, udp_input_port); } std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const { @@ -191,25 +176,6 @@ std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const { return MAX_UDP_CLIENTS; } -Common::Input::BatteryLevel UDPClient::GetBatteryLevel(Response::Battery battery) const { - switch (battery) { - case Response::Battery::Dying: - return Common::Input::BatteryLevel::Empty; - case Response::Battery::Low: - return Common::Input::BatteryLevel::Critical; - case Response::Battery::Medium: - return Common::Input::BatteryLevel::Low; - case Response::Battery::High: - return Common::Input::BatteryLevel::Medium; - case Response::Battery::Full: - case Response::Battery::Charged: - return Common::Input::BatteryLevel::Full; - case Response::Battery::Charging: - default: - return Common::Input::BatteryLevel::Charging; - } -} - void UDPClient::OnVersion([[maybe_unused]] Response::Version data) { LOG_TRACE(Input, "Version packet received: {}", data.version); } @@ -273,7 +239,7 @@ void UDPClient::OnPadData(Response::PadData data, std::size_t client) { static_cast(id == 0 ? PadButton::Touch1 : PadButton::Touch2); // TODO: Use custom calibration per device - const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); + /* const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); const u16 min_x = static_cast(touch_param.Get("min_x", 100)); const u16 min_y = static_cast(touch_param.Get("min_y", 50)); const u16 max_x = static_cast(touch_param.Get("max_x", 1800)); @@ -294,7 +260,7 @@ void UDPClient::OnPadData(Response::PadData data, std::size_t client) { } SetAxis(identifier, touch_axis_x_id, 0); SetAxis(identifier, touch_axis_y_id, 0); - SetButton(identifier, touch_button_id, false); + SetButton(identifier, touch_button_id, false);*/ } SetAxis(identifier, static_cast(PadAxes::LeftStickX), @@ -320,8 +286,6 @@ void UDPClient::OnPadData(Response::PadData data, std::size_t client) { SetButton(identifier, static_cast(PadButton::Home), data.home != 0); SetButton(identifier, static_cast(PadButton::TouchHardPress), data.touch_hard_press != 0); - - SetBattery(identifier, GetBatteryLevel(data.info.battery)); } void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) { @@ -368,28 +332,6 @@ void UDPClient::Reset() { std::vector UDPClient::GetInputDevices() const { std::vector devices; - if (!Settings::values.enable_udp_controller) { - return devices; - } - for (std::size_t client = 0; client < clients.size(); client++) { - if (clients[client].active != 1) { - continue; - } - for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { - const std::size_t pad_index = client * PADS_PER_CLIENT + index; - if (!pads[pad_index].connected) { - continue; - } - const auto pad_identifier = GetPadIdentifier(pad_index); - Common::ParamPackage identifier{}; - identifier.Set("engine", GetEngineName()); - identifier.Set("display", fmt::format("UDP Controller {}", pad_identifier.pad)); - identifier.Set("guid", pad_identifier.guid.RawString()); - identifier.Set("port", static_cast(pad_identifier.port)); - identifier.Set("pad", static_cast(pad_identifier.pad)); - devices.emplace_back(identifier); - } - } return devices; } @@ -446,7 +388,7 @@ AnalogMapping UDPClient::GetAnalogMappingForDevice(const Common::ParamPackage& p left_analog_params.Set("pad", params.Get("pad", 0)); left_analog_params.Set("axis_x", static_cast(PadAxes::LeftStickX)); left_analog_params.Set("axis_y", static_cast(PadAxes::LeftStickY)); - mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); + mapping.insert_or_assign(Settings::NativeAnalog::CirclePad, std::move(left_analog_params)); Common::ParamPackage right_analog_params; right_analog_params.Set("engine", GetEngineName()); @@ -455,7 +397,7 @@ AnalogMapping UDPClient::GetAnalogMappingForDevice(const Common::ParamPackage& p right_analog_params.Set("pad", params.Get("pad", 0)); right_analog_params.Set("axis_x", static_cast(PadAxes::RightStickX)); right_analog_params.Set("axis_y", static_cast(PadAxes::RightStickY)); - mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + mapping.insert_or_assign(Settings::NativeAnalog::CStick, std::move(right_analog_params)); return mapping; }