From 5219615418920be8502aa24507572cf0930d65ea Mon Sep 17 00:00:00 2001
From: Morph <39850852+Morph1984@users.noreply.github.com>
Date: Fri, 21 Aug 2020 07:39:24 -0400
Subject: Project Mjölnir: Part 2 - Controller Applet
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Its-Rei <kupfel@gmail.com>
---
 src/yuzu/applets/controller.cpp | 568 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 568 insertions(+)
 create mode 100644 src/yuzu/applets/controller.cpp

(limited to 'src/yuzu/applets/controller.cpp')

diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp
new file mode 100644
index 0000000000..8ccf61be0a
--- /dev/null
+++ b/src/yuzu/applets/controller.cpp
@@ -0,0 +1,568 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+
+#include "core/core.h"
+#include "core/hle/lock.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+#include "ui_controller.h"
+#include "yuzu/applets/controller.h"
+#include "yuzu/configuration/configure_input_dialog.h"
+#include "yuzu/main.h"
+
+constexpr std::array<std::array<bool, 4>, 8> led_patterns = {{
+    {1, 0, 0, 0},
+    {1, 1, 0, 0},
+    {1, 1, 1, 0},
+    {1, 1, 1, 1},
+    {1, 0, 0, 1},
+    {1, 0, 1, 0},
+    {1, 0, 1, 1},
+    {0, 1, 1, 0},
+}};
+
+namespace {
+
+void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
+                      bool connected) {
+    Core::System& system{Core::System::GetInstance()};
+
+    if (!system.IsPoweredOn()) {
+        return;
+    }
+
+    Service::SM::ServiceManager& sm = system.ServiceManager();
+
+    auto& npad =
+        sm.GetService<Service::HID::Hid>("hid")
+            ->GetAppletResource()
+            ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+    npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
+}
+
+bool IsControllerCompatible(Settings::ControllerType controller_type,
+                            Core::Frontend::ControllerParameters parameters) {
+    switch (controller_type) {
+    case Settings::ControllerType::ProController:
+        return parameters.allow_pro_controller;
+    case Settings::ControllerType::DualJoyconDetached:
+        return parameters.allow_dual_joycons;
+    case Settings::ControllerType::LeftJoycon:
+        return parameters.allow_left_joycon;
+    case Settings::ControllerType::RightJoycon:
+        return parameters.allow_right_joycon;
+    case Settings::ControllerType::Handheld:
+        return parameters.enable_single_mode && parameters.allow_handheld;
+    default:
+        return false;
+    }
+}
+
+/// 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;
+    }
+}
+
+} // namespace
+
+QtControllerSelectorDialog::QtControllerSelectorDialog(
+    QWidget* parent, Core::Frontend::ControllerParameters parameters_, InputCommon::InputSubsystem* input_subsystem_)
+    : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
+      parameters(std::move(parameters_)), input_subsystem(input_subsystem_) {
+    ui->setupUi(this);
+
+    player_widgets = {
+        ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4,
+        ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8,
+    };
+
+    player_groupboxes = {
+        ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected,
+        ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected,
+        ui->groupPlayer7Connected, ui->groupPlayer8Connected,
+    };
+
+    connected_controller_icons = {
+        ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4,
+        ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8,
+    };
+
+    led_patterns_boxes = {{
+        {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3,
+         ui->checkboxPlayer1LED4},
+        {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3,
+         ui->checkboxPlayer2LED4},
+        {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3,
+         ui->checkboxPlayer3LED4},
+        {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3,
+         ui->checkboxPlayer4LED4},
+        {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3,
+         ui->checkboxPlayer5LED4},
+        {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3,
+         ui->checkboxPlayer6LED4},
+        {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3,
+         ui->checkboxPlayer7LED4},
+        {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3,
+         ui->checkboxPlayer8LED4},
+    }};
+
+    emulated_controllers = {
+        ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated,
+        ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated,
+        ui->comboPlayer7Emulated, ui->comboPlayer8Emulated,
+    };
+
+    player_labels = {
+        ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4,
+        ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8,
+    };
+
+    connected_controller_labels = {
+        ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3,
+        ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6,
+        ui->labelConnectedPlayer7, ui->labelConnectedPlayer8,
+    };
+
+    connected_controller_checkboxes = {
+        ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
+        ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
+        ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
+    };
+
+    for (std::size_t i = 0; i < player_widgets.size(); ++i) {
+        connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
+            if (checked) {
+                for (std::size_t index = 0; index <= i; ++index) {
+                    connected_controller_checkboxes[index]->setChecked(checked);
+                }
+            } else {
+                for (std::size_t index = i; index < player_widgets.size(); ++index) {
+                    connected_controller_checkboxes[index]->setChecked(checked);
+                }
+            }
+        });
+
+        connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
+                [this, i](int) {
+                    UpdateControllerIcon(i);
+                    UpdateControllerState(i);
+                    UpdateLEDPattern(i);
+                    CheckIfParametersMet();
+                });
+
+        connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
+            player_groupboxes[i]->setChecked(state == Qt::Checked);
+            UpdateControllerIcon(i);
+            UpdateControllerState(i);
+            UpdateLEDPattern(i);
+            UpdateBorderColor(i);
+            CheckIfParametersMet();
+        });
+
+        if (i == 0) {
+            connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
+                    [this](int index) {
+                        UpdateDockedState(GetControllerTypeFromIndex(index) ==
+                                          Settings::ControllerType::Handheld);
+                    });
+        }
+    }
+
+    connect(ui->inputConfigButton, &QPushButton::clicked, this,
+            &QtControllerSelectorDialog::CallConfigureInputDialog);
+
+    connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
+            &QtControllerSelectorDialog::ApplyConfiguration);
+
+    SetSupportedControllers();
+    DisableUnsupportedPlayers();
+    LoadConfiguration();
+
+    // If keep_controllers_connected is false, forcefully disconnect all controllers
+    if (!parameters.keep_controllers_connected) {
+        for (auto player : player_groupboxes) {
+            player->setChecked(false);
+        }
+    }
+
+    CheckIfParametersMet();
+
+    resize(0, 0);
+}
+
+QtControllerSelectorDialog::~QtControllerSelectorDialog() = default;
+
+void QtControllerSelectorDialog::ApplyConfiguration() {
+    const bool pre_docked_mode = Settings::values.use_docked_mode;
+    Settings::values.use_docked_mode = ui->radioDocked->isChecked();
+    OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
+
+    Settings::values.vibration_enabled = ui->vibrationGroup->isChecked();
+}
+
+void QtControllerSelectorDialog::CallConfigureInputDialog() {
+    const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+    ConfigureInputDialog dialog(this, max_supported_players, input_subsystem);
+
+    dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+                          Qt::WindowSystemMenuHint);
+    dialog.setWindowModality(Qt::WindowModal);
+    dialog.exec();
+
+    dialog.ApplyConfiguration();
+
+    LoadConfiguration();
+    CheckIfParametersMet();
+}
+
+void QtControllerSelectorDialog::CheckIfParametersMet() {
+    // Here, we check and validate the current configuration against all applicable parameters.
+    const auto& players = Settings::values.players;
+
+    const auto num_connected_players =
+        std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
+                      [this](const QGroupBox* player) { return player->isChecked(); });
+
+    const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
+    const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+    // First, check against the number of connected players.
+    if (num_connected_players < min_supported_players ||
+        num_connected_players > max_supported_players) {
+        parameters_met = false;
+        ui->buttonBox->setEnabled(parameters_met);
+        return;
+    }
+
+    // Next, check against all connected controllers.
+    const auto all_controllers_compatible = [this] {
+        for (std::size_t index = 0; index < player_widgets.size(); ++index) {
+            // Skip controllers that are not used, we only care about the currently connected ones.
+            if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) {
+                continue;
+            }
+
+            const auto compatible = IsControllerCompatible(
+                GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex()),
+                parameters);
+
+            // If any controller is found to be incompatible, return false early.
+            if (!compatible) {
+                return false;
+            }
+        }
+
+        // Reaching here means all currently connected controllers are compatible.
+        return true;
+    }();
+
+    if (!all_controllers_compatible) {
+        parameters_met = false;
+        ui->buttonBox->setEnabled(parameters_met);
+        return;
+    }
+
+    parameters_met = true;
+    ui->buttonBox->setEnabled(parameters_met);
+}
+
+void QtControllerSelectorDialog::SetSupportedControllers() {
+    const QString theme = [this] {
+        if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+            return QStringLiteral("_dark");
+        } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+            return QStringLiteral("_midnight");
+        } else {
+            return QString{};
+        }
+    }();
+
+    if (parameters.enable_single_mode && parameters.allow_handheld) {
+        ui->controllerSupported1->setStyleSheet(
+            QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme));
+    } else {
+        ui->controllerSupported1->setStyleSheet(
+            QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme));
+    }
+
+    if (parameters.allow_dual_joycons) {
+        ui->controllerSupported2->setStyleSheet(
+            QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme));
+    } else {
+        ui->controllerSupported2->setStyleSheet(
+            QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme));
+    }
+
+    if (parameters.allow_left_joycon) {
+        ui->controllerSupported3->setStyleSheet(
+            QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme));
+    } else {
+        ui->controllerSupported3->setStyleSheet(
+            QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme));
+    }
+
+    if (parameters.allow_right_joycon) {
+        ui->controllerSupported4->setStyleSheet(
+            QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme));
+    } else {
+        ui->controllerSupported4->setStyleSheet(
+            QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
+    }
+
+    if (parameters.allow_pro_controller) {
+        ui->controllerSupported5->setStyleSheet(
+            QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
+    } else {
+        ui->controllerSupported5->setStyleSheet(
+            QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ")
+                .arg(theme));
+    }
+
+    // enable_single_mode overrides min_players and max_players.
+    if (parameters.enable_single_mode) {
+        ui->numberSupportedLabel->setText(QStringLiteral("1"));
+        return;
+    }
+
+    if (parameters.min_players == parameters.max_players) {
+        ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players));
+    } else {
+        ui->numberSupportedLabel->setText(
+            QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players));
+    }
+}
+
+void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) {
+    if (!player_groupboxes[player_index]->isChecked()) {
+        connected_controller_icons[player_index]->setStyleSheet(QString{});
+        player_labels[player_index]->show();
+        return;
+    }
+
+    const QString stylesheet = [this, player_index] {
+        switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex())) {
+        case Settings::ControllerType::ProController:
+            return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
+        case Settings::ControllerType::DualJoyconDetached:
+            return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
+        case Settings::ControllerType::LeftJoycon:
+            return QStringLiteral("image: url(:/controller/applet_joycon_left%0); ");
+        case Settings::ControllerType::RightJoycon:
+            return QStringLiteral("image: url(:/controller/applet_joycon_right%0); ");
+        case Settings::ControllerType::Handheld:
+            return QStringLiteral("image: url(:/controller/applet_handheld%0); ");
+        default:
+            return QString{};
+        }
+    }();
+
+    const QString theme = [this] {
+        if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+            return QStringLiteral("_dark");
+        } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+            return QStringLiteral("_midnight");
+        } else {
+            return QString{};
+        }
+    }();
+
+    connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme));
+    player_labels[player_index]->hide();
+}
+
+void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
+    auto& player = Settings::values.players[player_index];
+
+    player.controller_type =
+        GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex());
+    player.connected = player_groupboxes[player_index]->isChecked();
+
+    // Player 2-8
+    if (player_index != 0) {
+        UpdateController(player.controller_type, player_index, player.connected);
+        return;
+    }
+
+    // Player 1 and Handheld
+    auto& handheld = Settings::values.players[8];
+    // If Handheld is selected, copy all the settings from Player 1 to Handheld.
+    if (player.controller_type == Settings::ControllerType::Handheld) {
+        handheld = player;
+        handheld.connected = player_groupboxes[player_index]->isChecked();
+        player.connected = false; // Disconnect Player 1
+    } else {
+        player.connected = player_groupboxes[player_index]->isChecked();
+        handheld.connected = false; // Disconnect Handheld
+    }
+
+    UpdateController(player.controller_type, player_index, player.connected);
+    UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected);
+}
+
+void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
+    if (!player_groupboxes[player_index]->isChecked() ||
+        GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()) ==
+            Settings::ControllerType::Handheld) {
+        led_patterns_boxes[player_index][0]->setChecked(false);
+        led_patterns_boxes[player_index][1]->setChecked(false);
+        led_patterns_boxes[player_index][2]->setChecked(false);
+        led_patterns_boxes[player_index][3]->setChecked(false);
+        return;
+    }
+
+    led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]);
+    led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]);
+    led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]);
+    led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]);
+}
+
+void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) {
+    if (!parameters.enable_border_color || player_index >= parameters.max_players ||
+        player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) {
+        return;
+    }
+
+    player_groupboxes[player_index]->setStyleSheet(
+        player_groupboxes[player_index]->styleSheet().append(
+            QStringLiteral("QGroupBox#groupPlayer%1Connected:checked "
+                           "{ border: 1px solid rgba(%2, %3, %4, %5); }")
+                .arg(player_index + 1)
+                .arg(parameters.border_colors[player_index][0])
+                .arg(parameters.border_colors[player_index][1])
+                .arg(parameters.border_colors[player_index][2])
+                .arg(parameters.border_colors[player_index][3])));
+}
+
+void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
+    // Disallow changing the console mode if the controller type is handheld.
+    ui->radioDocked->setEnabled(!is_handheld);
+    ui->radioUndocked->setEnabled(!is_handheld);
+
+    ui->radioDocked->setChecked(Settings::values.use_docked_mode);
+    ui->radioUndocked->setChecked(!Settings::values.use_docked_mode);
+
+    // Also force into undocked mode if the controller type is handheld.
+    if (is_handheld) {
+        ui->radioUndocked->setChecked(true);
+    }
+}
+
+void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
+    const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+    switch (max_supported_players) {
+    case 0:
+    default:
+        UNREACHABLE();
+        return;
+    case 1:
+        ui->widgetSpacer->hide();
+        ui->widgetSpacer2->hide();
+        ui->widgetSpacer3->hide();
+        ui->widgetSpacer4->hide();
+        break;
+    case 2:
+        ui->widgetSpacer->hide();
+        ui->widgetSpacer2->hide();
+        ui->widgetSpacer3->hide();
+        break;
+    case 3:
+        ui->widgetSpacer->hide();
+        ui->widgetSpacer2->hide();
+        break;
+    case 4:
+        ui->widgetSpacer->hide();
+        break;
+    case 5:
+    case 6:
+    case 7:
+    case 8:
+        break;
+    }
+
+    for (std::size_t index = max_supported_players; index < player_widgets.size(); ++index) {
+        // Disconnect any unsupported players here and disable or hide them if applicable.
+        Settings::values.players[index].connected = false;
+        UpdateController(Settings::values.players[index].controller_type, index, false);
+        // Hide the player widgets when max_supported_controllers is less than or equal to 4.
+        if (max_supported_players <= 4) {
+            player_widgets[index]->hide();
+        }
+
+        // Disable and hide the following to prevent these from interaction.
+        player_widgets[index]->setDisabled(true);
+        connected_controller_checkboxes[index]->setDisabled(true);
+        connected_controller_labels[index]->hide();
+        connected_controller_checkboxes[index]->hide();
+    }
+}
+
+void QtControllerSelectorDialog::LoadConfiguration() {
+    for (std::size_t index = 0; index < player_widgets.size(); ++index) {
+        const auto connected = Settings::values.players[index].connected ||
+                               (index == 0 && Settings::values.players[8].connected);
+        player_groupboxes[index]->setChecked(connected);
+        emulated_controllers[index]->setCurrentIndex(
+            GetIndexFromControllerType(Settings::values.players[index].controller_type));
+    }
+
+    UpdateDockedState(Settings::values.players[8].connected);
+
+    ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
+}
+
+QtControllerSelector::QtControllerSelector(GMainWindow& parent) {
+    connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent,
+            &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection);
+    connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this,
+            &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection);
+}
+
+QtControllerSelector::~QtControllerSelector() = default;
+
+void QtControllerSelector::ReconfigureControllers(
+    std::function<void()> callback, Core::Frontend::ControllerParameters parameters) const {
+    this->callback = std::move(callback);
+    emit MainWindowReconfigureControllers(parameters);
+}
+
+void QtControllerSelector::MainWindowReconfigureFinished() {
+    // Acquire the HLE mutex
+    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+    callback();
+}
-- 
cgit v1.2.3-70-g09d2