code:
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "yuzu/configuration/configure_ui.h"

#include <array>
#include <cstdlib>
#include <set>
#include <stdexcept>
#include <string>
#include <utility>

#include <QCheckBox>
#include <QComboBox>
#include <QCoreApplication>
#include <QDirIterator>
#include <QFileDialog>
#include <QString>
#include <QToolButton>
#include <QVariant>

#include "common/common_types.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "common/settings_enums.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
#include "ui_configure_ui.h"
#include "yuzu/uisettings.h"

namespace {
constexpr std::array default_game_icon_sizes{
    std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")),
    std::make_pair(32, QT_TRANSLATE_NOOP("ConfigureUI", "Small (32x32)")),
    std::make_pair(64, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (64x64)")),
    std::make_pair(128, QT_TRANSLATE_NOOP("ConfigureUI", "Large (128x128)")),
    std::make_pair(256, QT_TRANSLATE_NOOP("ConfigureUI", "Full Size (256x256)")),
};

constexpr std::array default_folder_icon_sizes{
    std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")),
    std::make_pair(24, QT_TRANSLATE_NOOP("ConfigureUI", "Small (24x24)")),
    std::make_pair(48, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (48x48)")),
    std::make_pair(72, QT_TRANSLATE_NOOP("ConfigureUI", "Large (72x72)")),
};

// clang-format off
constexpr std::array row_text_names{
    QT_TRANSLATE_NOOP("ConfigureUI", "Filename"),
    QT_TRANSLATE_NOOP("ConfigureUI", "Filetype"),
    QT_TRANSLATE_NOOP("ConfigureUI", "Title ID"),
    QT_TRANSLATE_NOOP("ConfigureUI", "Title Name"),
    QT_TRANSLATE_NOOP("ConfigureUI", "None"),
};
// clang-format on

QString GetTranslatedGameIconSize(size_t index) {
    return QCoreApplication::translate("ConfigureUI", default_game_icon_sizes[index].second);
}

QString GetTranslatedFolderIconSize(size_t index) {
    return QCoreApplication::translate("ConfigureUI", default_folder_icon_sizes[index].second);
}

QString GetTranslatedRowTextName(size_t index) {
    return QCoreApplication::translate("ConfigureUI", row_text_names[index]);
}
} // Anonymous namespace

static float GetUpFactor(Settings::ResolutionSetup res_setup) {
    Settings::ResolutionScalingInfo info{};
    Settings::TranslateResolutionInfo(res_setup, info);
    return info.up_factor;
}

static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* parent) {
    screenshot_height->clear();

    const auto& enumeration =
        Settings::EnumMetadata<Settings::ResolutionSetup>::Canonicalizations();
    std::set<u32> resolutions{};
    for (const auto& [name, value] : enumeration) {
        const float up_factor = GetUpFactor(value);
        u32 height_undocked = Layout::ScreenUndocked::Height * up_factor;
        u32 height_docked = Layout::ScreenDocked::Height * up_factor;
        resolutions.emplace(height_undocked);
        resolutions.emplace(height_docked);
    }

    screenshot_height->addItem(parent->tr("Auto", "Screenshot height option"));
    for (const auto res : resolutions) {
        screenshot_height->addItem(QString::fromStdString(std::to_string(res)));
    }
}

static u32 ScreenshotDimensionToInt(const QString& height) {
    return std::strtoul(height.toUtf8(), nullptr, 0);
}

ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
    : QWidget(parent), ui{std::make_unique<Ui::ConfigureUi>()},
      ratio{Settings::values.aspect_ratio.GetValue()},
      resolution_setting{Settings::values.resolution_setup.GetValue()}, system{system_} {
    ui->setupUi(this);

    InitializeLanguageComboBox();

    for (const auto& theme : UISettings::themes) {
        ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
                                    QString::fromUtf8(theme.second));
    }

    InitializeIconSizeComboBox();
    InitializeRowComboBoxes();

    PopulateResolutionComboBox(ui->screenshot_height, this);

    SetConfiguration();

    // Force game list reload if any of the relevant settings are changed.
    connect(ui->show_add_ons, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
    connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
    connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
    connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
    connect(ui->show_play_time, &QCheckBox::stateChanged, this,
            &ConfigureUi::RequestGameListUpdate);
    connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
            &ConfigureUi::RequestGameListUpdate);
    connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &ConfigureUi::RequestGameListUpdate);
    connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
            &ConfigureUi::RequestGameListUpdate);
    connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
            &ConfigureUi::RequestGameListUpdate);

    // Update text ComboBoxes after user interaction.
    connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::activated),
            [this] { ConfigureUi::UpdateSecondRowComboBox(); });
    connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::activated),
            [this] { ConfigureUi::UpdateFirstRowComboBox(); });

    // Set screenshot path to user specification.
    connect(ui->screenshot_path_button, &QToolButton::pressed, this, [this] {
        auto dir =
            QFileDialog::getExistingDirectory(this, tr("Select Screenshots Path..."),
                                              QString::fromStdString(Common::FS::GetYuzuPathString(
                                                  Common::FS::YuzuPath::ScreenshotsDir)));
        if (!dir.isEmpty()) {
            if (dir.back() != QChar::fromLatin1('/')) {
                dir.append(QChar::fromLatin1('/'));
            }

            ui->screenshot_path_edit->setText(dir);
        }
    });

    connect(ui->screenshot_height, &QComboBox::currentTextChanged, [this]() { UpdateWidthText(); });

    UpdateWidthText();
}

ConfigureUi::~ConfigureUi() = default;

void ConfigureUi::ApplyConfiguration() {
    UISettings::values.theme =
        ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString().toStdString();
    UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
    UISettings::values.show_compat = ui->show_compat->isChecked();
    UISettings::values.show_size = ui->show_size->isChecked();
    UISettings::values.show_types = ui->show_types->isChecked();
    UISettings::values.show_play_time = ui->show_play_time->isChecked();
    UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
    UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
    UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
    UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt();

    UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked();
    Common::FS::SetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir,
                            ui->screenshot_path_edit->text().toStdString());

    const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
    UISettings::values.screenshot_height.SetValue(height);

    RequestGameListUpdate();
    system.ApplySettings();
}

void ConfigureUi::RequestGameListUpdate() {
    UISettings::values.is_game_list_reload_pending.exchange(true);
}

void ConfigureUi::SetConfiguration() {
    ui->theme_combobox->setCurrentIndex(
        ui->theme_combobox->findData(QString::fromStdString(UISettings::values.theme)));
    ui->language_combobox->setCurrentIndex(ui->language_combobox->findData(
        QString::fromStdString(UISettings::values.language.GetValue())));
    ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue());
    ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
    ui->show_size->setChecked(UISettings::values.show_size.GetValue());
    ui->show_types->setChecked(UISettings::values.show_types.GetValue());
    ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue());
    ui->game_icon_size_combobox->setCurrentIndex(
        ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
    ui->folder_icon_size_combobox->setCurrentIndex(
        ui->folder_icon_size_combobox->findData(UISettings::values.folder_icon_size.GetValue()));

    ui->enable_screenshot_save_as->setChecked(
        UISettings::values.enable_screenshot_save_as.GetValue());
    ui->screenshot_path_edit->setText(QString::fromStdString(
        Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir)));

    const auto height = UISettings::values.screenshot_height.GetValue();
    if (height == 0) {
        ui->screenshot_height->setCurrentIndex(0);
    } else {
        ui->screenshot_height->setCurrentText(QStringLiteral("%1").arg(height));
    }
}

void ConfigureUi::changeEvent(QEvent* event) {
    if (event->type() == QEvent::LanguageChange) {
        RetranslateUI();
    }

    QWidget::changeEvent(event);
}

void ConfigureUi::RetranslateUI() {
    ui->retranslateUi(this);

    for (int i = 0; i < ui->game_icon_size_combobox->count(); i++) {
        ui->game_icon_size_combobox->setItemText(i,
                                                 GetTranslatedGameIconSize(static_cast<size_t>(i)));
    }

    for (int i = 0; i < ui->folder_icon_size_combobox->count(); i++) {
        ui->folder_icon_size_combobox->setItemText(
            i, GetTranslatedFolderIconSize(static_cast<size_t>(i)));
    }

    for (int i = 0; i < ui->row_1_text_combobox->count(); i++) {
        const QString name = GetTranslatedRowTextName(static_cast<size_t>(i));

        ui->row_1_text_combobox->setItemText(i, name);
        ui->row_2_text_combobox->setItemText(i, name);
    }
}

void ConfigureUi::InitializeLanguageComboBox() {
    ui->language_combobox->addItem(tr("<System>"), QString{});
    ui->language_combobox->addItem(tr("English"), QStringLiteral("en"));
    QDirIterator it(QStringLiteral(":/languages"), QDirIterator::NoIteratorFlags);
    while (it.hasNext()) {
        QString locale = it.next();
        locale.truncate(locale.lastIndexOf(QLatin1Char{'.'}));
        locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1);
        const QString lang = QLocale::languageToString(QLocale(locale).language());
        const QString country = QLocale::countryToString(QLocale(locale).country());
        ui->language_combobox->addItem(QStringLiteral("%1 (%2)").arg(lang, country), locale);
    }

    // Unlike other configuration changes, interface language changes need to be reflected on the
    // interface immediately. This is done by passing a signal to the main window, and then
    // retranslating when passing back.
    connect(ui->language_combobox, qOverload<int>(&QComboBox::currentIndexChanged), this,
            &ConfigureUi::OnLanguageChanged);
}

void ConfigureUi::InitializeIconSizeComboBox() {
    for (size_t i = 0; i < default_game_icon_sizes.size(); i++) {
        const auto size = default_game_icon_sizes[i].first;
        ui->game_icon_size_combobox->addItem(GetTranslatedGameIconSize(i), size);
    }
    for (size_t i = 0; i < default_folder_icon_sizes.size(); i++) {
        const auto size = default_folder_icon_sizes[i].first;
        ui->folder_icon_size_combobox->addItem(GetTranslatedFolderIconSize(i), size);
    }
}

void ConfigureUi::InitializeRowComboBoxes() {
    UpdateFirstRowComboBox(true);
    UpdateSecondRowComboBox(true);
}

void ConfigureUi::UpdateFirstRowComboBox(bool init) {
    const int currentIndex =
        init ? UISettings::values.row_1_text_id.GetValue()
             : ui->row_1_text_combobox->findData(ui->row_1_text_combobox->currentData());

    ui->row_1_text_combobox->clear();

    for (std::size_t i = 0; i < row_text_names.size(); i++) {
        const QString row_text_name = GetTranslatedRowTextName(i);
        ui->row_1_text_combobox->addItem(row_text_name, QVariant::fromValue(i));
    }

    ui->row_1_text_combobox->setCurrentIndex(ui->row_1_text_combobox->findData(currentIndex));

    ui->row_1_text_combobox->removeItem(4); // None
    ui->row_1_text_combobox->removeItem(
        ui->row_1_text_combobox->findData(ui->row_2_text_combobox->currentData()));
}

void ConfigureUi::UpdateSecondRowComboBox(bool init) {
    const int currentIndex =
        init ? UISettings::values.row_2_text_id.GetValue()
             : ui->row_2_text_combobox->findData(ui->row_2_text_combobox->currentData());

    ui->row_2_text_combobox->clear();

    for (std::size_t i = 0; i < row_text_names.size(); ++i) {
        const QString row_text_name = GetTranslatedRowTextName(i);
        ui->row_2_text_combobox->addItem(row_text_name, QVariant::fromValue(i));
    }

    ui->row_2_text_combobox->setCurrentIndex(ui->row_2_text_combobox->findData(currentIndex));

    ui->row_2_text_combobox->removeItem(
        ui->row_2_text_combobox->findData(ui->row_1_text_combobox->currentData()));
}

void ConfigureUi::OnLanguageChanged(int index) {
    if (index == -1)
        return;

    emit LanguageChanged(ui->language_combobox->itemData(index).toString());
}

void ConfigureUi::UpdateWidthText() {
    const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
    const u32 width = UISettings::CalculateWidth(height, ratio);
    if (height == 0) {
        const auto up_factor = GetUpFactor(resolution_setting);
        const u32 height_docked = Layout::ScreenDocked::Height * up_factor;
        const u32 width_docked = UISettings::CalculateWidth(height_docked, ratio);
        const u32 height_undocked = Layout::ScreenUndocked::Height * up_factor;
        const u32 width_undocked = UISettings::CalculateWidth(height_undocked, ratio);
        ui->screenshot_width->setText(tr("Auto (%1 x %2, %3 x %4)", "Screenshot width value")
                                          .arg(width_undocked)
                                          .arg(height_undocked)
                                          .arg(width_docked)
                                          .arg(height_docked));
    } else {
        ui->screenshot_width->setText(QStringLiteral("%1 x").arg(width));
    }
}

void ConfigureUi::UpdateScreenshotInfo(Settings::AspectRatio ratio_,
                                       Settings::ResolutionSetup resolution_setting_) {
    ratio = ratio_;
    resolution_setting = resolution_setting_;
    UpdateWidthText();
}


code:
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>
#include <QWidget>
#include "common/settings_enums.h"

namespace Core {
class System;
}

namespace Ui {
class ConfigureUi;
}

class ConfigureUi : public QWidget {
    Q_OBJECT

public:
    explicit ConfigureUi(Core::System& system_, QWidget* parent = nullptr);
    ~ConfigureUi() override;

    void ApplyConfiguration();

    void UpdateScreenshotInfo(Settings::AspectRatio ratio,
                              Settings::ResolutionSetup resolution_info);

private slots:
    void OnLanguageChanged(int index);

signals:
    void LanguageChanged(const QString& locale);

private:
    void RequestGameListUpdate();

    void SetConfiguration();

    void changeEvent(QEvent*) override;
    void RetranslateUI();

    void InitializeLanguageComboBox();
    void InitializeIconSizeComboBox();
    void InitializeRowComboBoxes();

    void UpdateFirstRowComboBox(bool init = false);
    void UpdateSecondRowComboBox(bool init = false);

    void UpdateWidthText();

    std::unique_ptr<Ui::ConfigureUi> ui;

    Settings::AspectRatio ratio;
    Settings::ResolutionSetup resolution_setting;
    Core::System& system;
};

repo_name: litucks/torzu

license: mit


code:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ConfigureUi</class>
 <widget class="QWidget" name="ConfigureUi">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>363</width>
    <height>603</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <property name="accessibleName">
   <string>UI</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QGroupBox" name="general_groupBox">
     <property name="title">
      <string>General</string>
     </property>
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
       <layout class="QVBoxLayout" name="verticalLayout_2">
        <item>
         <widget class="QLabel" name="label_change_language_info">
          <property name="text">
           <string>Note: Changing language will apply your configuration.</string>
          </property>
          <property name="wordWrap">
           <bool>true</bool>
          </property>
         </widget>
        </item>
        <item>
         <layout class="QHBoxLayout" name="horizontalLayout_2">
          <item>
           <widget class="QLabel" name="language_label">
            <property name="text">
             <string>Interface language:</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QComboBox" name="language_combobox"/>
          </item>
         </layout>
        </item>
        <item>
         <layout class="QHBoxLayout" name="horizontalLayout_3">
          <item>
           <widget class="QLabel" name="theme_label">
            <property name="text">
             <string>Theme:</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QComboBox" name="theme_combobox"/>
          </item>
         </layout>
        </item>
       </layout>
      </item>
     </layout>
    </widget>
   </item>
   <item>
    <widget class="QGroupBox" name="GameListGroupBox">
     <property name="title">
      <string>Game List</string>
     </property>
     <layout class="QHBoxLayout" name="GameListHorizontalLayout">
      <item>
       <layout class="QVBoxLayout" name="GeneralVerticalLayout">
        <item>
         <widget class="QCheckBox" name="show_compat">
          <property name="text">
           <string>Show Compatibility List</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QCheckBox" name="show_add_ons">
          <property name="text">
           <string>Show Add-Ons Column</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QCheckBox" name="show_size">
          <property name="text">
           <string>Show Size Column</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QCheckBox" name="show_types">
          <property name="text">
           <string>Show File Types Column</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QCheckBox" name="show_play_time">
          <property name="text">
           <string>Show Play Time Column</string>
          </property>
         </widget>
        </item>
        <item>
         <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
          <item>
           <widget class="QLabel" name="game_icon_size_label">
            <property name="text">
             <string>Game Icon Size:</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QComboBox" name="game_icon_size_combobox"/>
          </item>
         </layout>
        </item>
        <item>
         <layout class="QHBoxLayout" name="folder_icon_size_qhbox_layout_2">
          <item>
           <widget class="QLabel" name="folder_icon_size_label">
            <property name="text">
             <string>Folder Icon Size:</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QComboBox" name="folder_icon_size_combobox"/>
          </item>
         </layout>
        </item>
        <item>
         <layout class="QHBoxLayout" name="row_1_qhbox_layout">
          <item>
           <widget class="QLabel" name="row_1_label">
            <property name="text">
             <string>Row 1 Text:</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QComboBox" name="row_1_text_combobox"/>
          </item>
         </layout>
        </item>
        <item>
         <layout class="QHBoxLayout" name="row_2_qhbox_layout">
          <item>
           <widget class="QLabel" name="row_2_label">
            <property name="text">
             <string>Row 2 Text:</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QComboBox" name="row_2_text_combobox"/>
          </item>
         </layout>
        </item>
       </layout>
      </item>
     </layout>
    </widget>
   </item>
   <item>
    <widget class="QGroupBox" name="screenshots_GroupBox">
     <property name="title">
      <string>Screenshots</string>
     </property>
     <layout class="QVBoxLayout" name="verticalLayout_4">
      <item>
       <layout class="QVBoxLayout" name="verticalLayout_3">
        <item>
         <widget class="QCheckBox" name="enable_screenshot_save_as">
          <property name="text">
           <string>Ask Where To Save Screenshots (Windows Only)</string>
          </property>
         </widget>
        </item>
        <item>
         <layout class="QHBoxLayout" name="horizontalLayout_4">
          <item>
           <widget class="QLabel" name="label">
            <property name="text">
             <string>Screenshots Path: </string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QLineEdit" name="screenshot_path_edit"/>
          </item>
          <item>
           <widget class="QToolButton" name="screenshot_path_button">
            <property name="text">
             <string>...</string>
            </property>
           </widget>
          </item>
         </layout>
        </item>
        <item>
         <layout class="QGridLayout" name="gridLayout">
          <property name="spacing">
           <number>6</number>
          </property>
          <item row="0" column="1">
           <layout class="QHBoxLayout" name="horizontalLayout_5">
            <item>
             <widget class="QLabel" name="screenshot_width">
              <property name="text">
               <string>TextLabel</string>
              </property>
              <property name="alignment">
               <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QComboBox" name="screenshot_height">
              <property name="editable">
               <bool>true</bool>
              </property>
             </widget>
            </item>
           </layout>
          </item>
          <item row="0" column="0">
           <widget class="QLabel" name="label_3">
            <property name="text">
             <string>Resolution:</string>
            </property>
           </widget>
          </item>
         </layout>
        </item>
       </layout>
      </item>
     </layout>
    </widget>
   </item>
   <item>
    <spacer name="verticalSpacer">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>


code:
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/settings.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h"
#include "ui_configure_vibration.h"
#include "yuzu/configuration/configure_vibration.h"

ConfigureVibration::ConfigureVibration(QWidget* parent, Core::HID::HIDCore& hid_core_)
    : QDialog(parent), ui(std::make_unique<Ui::ConfigureVibration>()), hid_core{hid_core_} {
    ui->setupUi(this);

    vibration_groupboxes = {
        ui->vibrationGroupPlayer1, ui->vibrationGroupPlayer2, ui->vibrationGroupPlayer3,
        ui->vibrationGroupPlayer4, ui->vibrationGroupPlayer5, ui->vibrationGroupPlayer6,
        ui->vibrationGroupPlayer7, ui->vibrationGroupPlayer8,
    };

    vibration_spinboxes = {
        ui->vibrationSpinPlayer1, ui->vibrationSpinPlayer2, ui->vibrationSpinPlayer3,
        ui->vibrationSpinPlayer4, ui->vibrationSpinPlayer5, ui->vibrationSpinPlayer6,
        ui->vibrationSpinPlayer7, ui->vibrationSpinPlayer8,
    };

    const auto& players = Settings::values.players.GetValue();

    for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
        auto controller = hid_core.GetEmulatedControllerByIndex(i);
        Core::HID::ControllerUpdateCallback engine_callback{
            .on_change = [this,
                          i](Core::HID::ControllerTriggerType type) { VibrateController(type, i); },
            .is_npad_service = false,
        };
        controller_callback_key[i] = controller->SetCallback(engine_callback);
        vibration_groupboxes[i]->setChecked(players[i].vibration_enabled);
        vibration_spinboxes[i]->setValue(players[i].vibration_strength);
    }

    ui->checkBoxAccurateVibration->setChecked(
        Settings::values.enable_accurate_vibrations.GetValue());

    if (!Settings::IsConfiguringGlobal()) {
        ui->checkBoxAccurateVibration->setDisabled(true);
    }

    RetranslateUI();
}

ConfigureVibration::~ConfigureVibration() {
    StopVibrations();

    for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
        auto controller = hid_core.GetEmulatedControllerByIndex(i);
        controller->DeleteCallback(controller_callback_key[i]);
    }
};

void ConfigureVibration::ApplyConfiguration() {
    auto& players = Settings::values.players.GetValue();

    for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
        players[i].vibration_enabled = vibration_groupboxes[i]->isChecked();
        players[i].vibration_strength = vibration_spinboxes[i]->value();
    }

    Settings::values.enable_accurate_vibrations.SetValue(
        ui->checkBoxAccurateVibration->isChecked());
}

void ConfigureVibration::changeEvent(QEvent* event) {
    if (event->type() == QEvent::LanguageChange) {
        RetranslateUI();
    }

    QDialog::changeEvent(event);
}

void ConfigureVibration::RetranslateUI() {
    ui->retranslateUi(this);
}

void ConfigureVibration::VibrateController(Core::HID::ControllerTriggerType type,
                                           std::size_t player_index) {
    if (type != Core::HID::ControllerTriggerType::Button) {
        return;
    }

    auto& player = Settings::values.players.GetValue()[player_index];
    auto controller = hid_core.GetEmulatedControllerByIndex(player_index);
    const int vibration_strength = vibration_spinboxes[player_index]->value();
    const auto& buttons = controller->GetButtonsValues();

    bool button_is_pressed = false;
    for (std::size_t i = 0; i < buttons.size(); ++i) {
        if (buttons[i].value) {
            button_is_pressed = true;
            break;
        }
    }

    if (!button_is_pressed) {
        StopVibrations();
        return;
    }

    const bool old_vibration_enabled = player.vibration_enabled;
    const int old_vibration_strength = player.vibration_strength;
    player.vibration_enabled = true;
    player.vibration_strength = vibration_strength;

    const Core::HID::VibrationValue vibration{
        .low_amplitude = 1.0f,
        .low_frequency = 160.0f,
        .high_amplitude = 1.0f,
        .high_frequency = 320.0f,
    };
    controller->SetVibration(Core::HID::DeviceIndex::Left, vibration);
    controller->SetVibration(Core::HID::DeviceIndex::Right, vibration);

    // Restore previous values
    player.vibration_enabled = old_vibration_enabled;
    player.vibration_strength = old_vibration_strength;
}

void ConfigureVibration::StopVibrations() {
    for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
        auto controller = hid_core.GetEmulatedControllerByIndex(i);
        controller->SetVibration(Core::HID::DeviceIndex::Left, Core::HID::DEFAULT_VIBRATION_VALUE);
        controller->SetVibration(Core::HID::DeviceIndex::Right, Core::HID::DEFAULT_VIBRATION_VALUE);
    }
}


code:
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <memory>
#include <QDialog>

class QGroupBox;
class QSpinBox;

namespace Ui {
class ConfigureVibration;
}

namespace Core::HID {
enum class ControllerTriggerType;
class HIDCore;
} // namespace Core::HID

class ConfigureVibration : public QDialog {
    Q_OBJECT

public:
    explicit ConfigureVibration(QWidget* parent, Core::HID::HIDCore& hid_core_);
    ~ConfigureVibration() override;

    void ApplyConfiguration();

private:
    void changeEvent(QEvent* event) override;
    void RetranslateUI();
    void VibrateController(Core::HID::ControllerTriggerType type, std::size_t player_index);
    void StopVibrations();

    std::unique_ptr<Ui::ConfigureVibration> ui;

    static constexpr std::size_t NUM_PLAYERS = 8;

    /// Groupboxes encapsulating the vibration strength spinbox.
    std::array<QGroupBox*, NUM_PLAYERS> vibration_groupboxes;

    /// Spinboxes representing the vibration strength percentage.
    std::array<QSpinBox*, NUM_PLAYERS> vibration_spinboxes;

    /// Callback index to stop the controllers events
    std::array<int, NUM_PLAYERS> controller_callback_key;

    Core::HID::HIDCore& hid_core;
};


code:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ConfigureVibration</class>
 <widget class="QDialog" name="ConfigureVibration">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>364</width>
    <height>242</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Configure Vibration</string>
  </property>
  <property name="styleSheet">
   <string notr="true"/>
  </property>
  <layout class="QVBoxLayout">
   <item row="0" column="0" colspan="4">
    <widget class="QLabel" name="label_1">
     <property name="text">
      <string>Press any controller button to vibrate the controller.</string>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QGroupBox" name="vibrationStrengthGroup">
     <property name="title">
      <string>Vibration</string>
     </property>
     <layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,0">
      <property name="leftMargin">
       <number>9</number>
      </property>
      <property name="topMargin">
       <number>9</number>
      </property>
      <property name="rightMargin">
       <number>9</number>
      </property>
      <property name="bottomMargin">
       <number>9</number>
      </property>
      <item>
       <widget class="QWidget" name="player14Widget" native="true">
        <layout class="QHBoxLayout" name="horizontalLayout_4">
         <property name="leftMargin">
          <number>0</number>
         </property>
         <property name="topMargin">
          <number>0</number>
         </property>
         <property name="rightMargin">
          <number>0</number>
         </property>
         <property name="bottomMargin">
          <number>0</number>
         </property>
         <item>
          <widget class="QGroupBox" name="vibrationGroupPlayer1">
           <property name="title">
            <string>Player 1</string>
           </property>
           <property name="checkable">
            <bool>true</bool>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_8">
            <property name="leftMargin">
             <number>3</number>
            </property>
            <property name="topMargin">
             <number>3</number>
            </property>
            <property name="rightMargin">
             <number>3</number>
            </property>
            <property name="bottomMargin">
             <number>3</number>
            </property>
            <item>
             <widget class="QSpinBox" name="vibrationSpinPlayer1">
              <property name="minimumSize">
               <size>
                <width>68</width>
                <height>21</height>
               </size>
              </property>
              <property name="maximumSize">
               <size>
                <width>68</width>
                <height>16777215</height>
               </size>
              </property>
              <property name="suffix">
               <string>%</string>
              </property>
              <property name="minimum">
               <number>1</number>
              </property>
              <property name="maximum">
               <number>150</number>
              </property>
              <property name="value">
               <number>100</number>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
         </item>
         <item>
          <widget class="QGroupBox" name="vibrationGroupPlayer2">
           <property name="title">
            <string>Player 2</string>
           </property>
           <property name="checkable">
            <bool>true</bool>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_9">
            <property name="leftMargin">
             <number>3</number>
            </property>
            <property name="topMargin">
             <number>3</number>
            </property>
            <property name="rightMargin">
             <number>3</number>
            </property>
            <property name="bottomMargin">
             <number>3</number>
            </property>
            <item>
             <widget class="QSpinBox" name="vibrationSpinPlayer2">
              <property name="minimumSize">
               <size>
                <width>68</width>
                <height>21</height>
               </size>
              </property>
              <property name="maximumSize">
               <size>
                <width>68</width>
                <height>16777215</height>
               </size>
              </property>
              <property name="suffix">
               <string>%</string>
              </property>
              <property name="minimum">
               <number>1</number>
              </property>
              <property name="maximum">
               <number>150</number>
              </property>
              <property name="value">
               <number>100</number>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
         </item>
         <item>
          <widget class="QGroupBox" name="vibrationGroupPlayer3">
           <property name="title">
            <string>Player 3</string>
           </property>
           <property name="checkable">
            <bool>true</bool>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_10">
            <property name="leftMargin">
             <number>3</number>
            </property>
            <property name="topMargin">
             <number>3</number>
            </property>
            <property name="rightMargin">
             <number>3</number>
            </property>
            <property name="bottomMargin">
             <number>3</number>
            </property>
            <item>
             <widget class="QSpinBox" name="vibrationSpinPlayer3">
              <property name="minimumSize">
               <size>
                <width>68</width>
                <height>21</height>
               </size>
              </property>
              <property name="maximumSize">
               <size>
                <width>68</width>
                <height>16777215</height>
               </size>
              </property>
              <property name="suffix">
               <string>%</string>
              </property>
              <property name="minimum">
               <number>1</number>
              </property>
              <property name="maximum">
               <number>150</number>
              </property>
              <property name="value">
               <number>100</number>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
         </item>
         <item>
          <widget class="QGroupBox" name="vibrationGroupPlayer4">
           <property name="title">
            <string>Player 4</string>
           </property>
           <property name="checkable">
            <bool>true</bool>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_11">
            <property name="leftMargin">
             <number>3</number>
            </property>
            <property name="topMargin">
             <number>3</number>
            </property>
            <property name="rightMargin">
             <number>3</number>
            </property>
            <property name="bottomMargin">
             <number>3</number>
            </property>
            <item>
             <widget class="QSpinBox" name="vibrationSpinPlayer4">
              <property name="minimumSize">
               <size>
                <width>68</width>
                <height>21</height>
               </size>
              </property>
              <property name="maximumSize">
               <size>
                <width>68</width>
                <height>16777215</height>
               </size>
              </property>
              <property name="suffix">
               <string>%</string>
              </property>
              <property name="minimum">
               <number>1</number>
              </property>
              <property name="maximum">
               <number>150</number>
              </property>
              <property name="value">
               <number>100</number>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
         </item>
        </layout>
       </widget>
      </item>
      <item>
       <widget class="QWidget" name="player58Widget" native="true">
        <layout class="QHBoxLayout" name="horizontalLayout_6">
         <property name="leftMargin">
          <number>0</number>
         </property>
         <property name="topMargin">
          <number>0</number>
         </property>
         <property name="rightMargin">
          <number>0</number>
         </property>
         <property name="bottomMargin">
          <number>0</number>
         </property>
         <item>
          <widget class="QGroupBox" name="vibrationGroupPlayer7">
           <property name="title">
            <string>Player 5</string>
           </property>
           <property name="checkable">
            <bool>true</bool>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_14">
            <property name="leftMargin">
             <number>3</number>
            </property>
            <property name="topMargin">
             <number>3</number>
            </property>
            <property name="rightMargin">
             <number>3</number>
            </property>
            <property name="bottomMargin">
             <number>3</number>
            </property>
            <item>
             <widget class="QSpinBox" name="vibrationSpinPlayer7">
              <property name="minimumSize">
               <size>
                <width>68</width>
                <height>21</height>
               </size>
              </property>
              <property name="maximumSize">
               <size>
                <width>68</width>
                <height>16777215</height>
               </size>
              </property>
              <property name="suffix">
               <string>%</string>
              </property>
              <property name="minimum">
               <number>1</number>
              </property>
              <property name="maximum">
               <number>150</number>
              </property>
              <property name="value">
               <number>100</number>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
         </item>
         <item>
          <widget class="QGroupBox" name="vibrationGroupPlayer8">
           <property name="title">
            <string>Player 6</string>
           </property>
           <property name="checkable">
            <bool>true</bool>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_15">
            <property name="leftMargin">
             <number>3</number>
            </property>
            <property name="topMargin">
             <number>3</number>
            </property>
            <property name="rightMargin">
             <number>3</number>
            </property>
            <property name="bottomMargin">
             <number>3</number>
            </property>
            <item>
             <widget class="QSpinBox" name="vibrationSpinPlayer8">
              <property name="minimumSize">
               <size>
                <width>68</width>
                <height>21</height>
               </size>
              </property>
              <property name="maximumSize">
               <size>
                <width>68</width>
                <height>16777215</height>
               </size>
              </property>
              <property name="suffix">
               <string>%</string>
              </property>
              <property name="minimum">
               <number>1</number>
              </property>
              <property name="maximum">
               <number>150</number>
              </property>
              <property name="value">
               <number>100</number>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
         </item>
         <item>
          <widget class="QGroupBox" name="vibrationGroupPlayer5">
           <property name="title">
            <string>Player 7</string>
           </property>
           <property name="checkable">
            <bool>true</bool>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_12">
            <property name="leftMargin">
             <number>3</number>
            </property>
            <property name="topMargin">
             <number>3</number>
            </property>
            <property name="rightMargin">
             <number>3</number>
            </property>
            <property name="bottomMargin">
             <number>3</number>
            </property>
            <item>
             <widget class="QSpinBox" name="vibrationSpinPlayer5">
              <property name="minimumSize">
               <size>
                <width>68</width>
                <height>21</height>
               </size>
              </property>
              <property name="maximumSize">
               <size>
                <width>68</width>
                <height>16777215</height>
               </size>
              </property>
              <property name="suffix">
               <string>%</string>
              </property>
              <property name="minimum">
               <number>1</number>
              </property>
              <property name="maximum">
               <number>150</number>
              </property>
              <property name="value">
               <number>100</number>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
         </item>
         <item>
          <widget class="QGroupBox" name="vibrationGroupPlayer6">
           <property name="title">
            <string>Player 8</string>
           </property>
           <property name="checkable">
            <bool>true</bool>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_13">
            <property name="leftMargin">
             <number>3</number>
            </property>
            <property name="topMargin">
             <number>3</number>
            </property>
            <property name="rightMargin">
             <number>3</number>
            </property>
            <property name="bottomMargin">
             <number>3</number>
            </property>
            <item>
             <widget class="QSpinBox" name="vibrationSpinPlayer6">
              <property name="minimumSize">
               <size>
                <width>68</width>
                <height>21</height>
               </size>
              </property>
              <property name="maximumSize">
               <size>
                <width>68</width>
                <height>16777215</height>
               </size>
              </property>
              <property name="suffix">
               <string>%</string>
              </property>
              <property name="minimum">
               <number>1</number>
              </property>
              <property name="maximum">
               <number>150</number>
              </property>
              <property name="value">
               <number>100</number>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
         </item>
        </layout>
       </widget>
      </item>
     </layout>
    </widget>
   </item>
   <item>
    <widget class="QGroupBox" name="vibrationSettingsGroup">
     <property name="title">
      <string>Settings</string>
     </property>
     <layout class="QVBoxLayout" name="verticalLayout">
      <item>
       <widget class="QCheckBox" name="checkBoxAccurateVibration">
        <property name="text">
         <string>Enable Accurate Vibration</string>
        </property>
       </widget>
      </item>
     </layout>
    </widget>
   </item>
   <item>
    <spacer name="spacerVibration">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>167</width>
       <height>55</height>
      </size>
     </property>
    </spacer>
   </item>
   <item>
    <widget class="QDialogButtonBox" name="buttonBoxVibration">
     <property name="standardButtons">
      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>buttonBoxVibration</sender>
   <signal>accepted()</signal>
   <receiver>ConfigureVibration</receiver>
   <slot>accept()</slot>
  </connection>
  <connection>
   <sender>buttonBoxVibration</sender>
   <signal>rejected()</signal>
   <receiver>ConfigureVibration</receiver>
   <slot>reject()</slot>
  </connection>
 </connections>
</ui>


code:
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <QIcon>
#include <QMessageBox>
#include <QtConcurrent/QtConcurrentRun>
#include "common/settings.h"
#include "core/telemetry_session.h"
#include "ui_configure_web.h"
#include "yuzu/configuration/configure_web.h"
#include "yuzu/uisettings.h"

static constexpr char token_delimiter{':'};

static std::string GenerateDisplayToken(const std::string& username, const std::string& token) {
    if (username.empty() || token.empty()) {
        return {};
    }

    const std::string unencoded_display_token{username + token_delimiter + token};
    QByteArray b{unencoded_display_token.c_str()};
    QByteArray b64 = b.toBase64();
    return b64.toStdString();
}

static std::string UsernameFromDisplayToken(const std::string& display_token) {
    const std::string unencoded_display_token{
        QByteArray::fromBase64(display_token.c_str()).toStdString()};
    return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter));
}

static std::string TokenFromDisplayToken(const std::string& display_token) {
    const std::string unencoded_display_token{
        QByteArray::fromBase64(display_token.c_str()).toStdString()};
    return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1);
}

ConfigureWeb::ConfigureWeb(QWidget* parent)
    : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
    ui->setupUi(this);
    connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
            &ConfigureWeb::RefreshTelemetryID);
    connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
    connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified);

#ifndef USE_DISCORD_PRESENCE
    ui->discord_group->setVisible(false);
#endif

    SetConfiguration();
    RetranslateUI();
}

ConfigureWeb::~ConfigureWeb() = default;

void ConfigureWeb::changeEvent(QEvent* event) {
    if (event->type() == QEvent::LanguageChange) {
        RetranslateUI();
    }

    QWidget::changeEvent(event);
}

void ConfigureWeb::RetranslateUI() {
    ui->retranslateUi(this);

    ui->telemetry_learn_more->setText(
        tr("Learn more"));

    ui->web_signup_link->setText(
        tr("Sign up"));

    ui->web_token_info_link->setText(
        tr("What is my token?"));

    ui->label_telemetry_id->setText(
        tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
}

void ConfigureWeb::SetConfiguration() {
    ui->web_credentials_disclaimer->setWordWrap(true);

    ui->telemetry_learn_more->setOpenExternalLinks(true);
    ui->web_signup_link->setOpenExternalLinks(true);
    ui->web_token_info_link->setOpenExternalLinks(true);

    if (Settings::values.yuzu_username.GetValue().empty()) {
        ui->username->setText(tr("Unspecified"));
    } else {
        ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue()));
    }

    ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry.GetValue());
    ui->edit_token->setText(QString::fromStdString(GenerateDisplayToken(
        Settings::values.yuzu_username.GetValue(), Settings::values.yuzu_token.GetValue())));

    // Connect after setting the values, to avoid calling OnLoginChanged now
    connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);

    user_verified = true;

    ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence.GetValue());
}

void ConfigureWeb::ApplyConfiguration() {
    Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
    UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
    if (user_verified) {
        Settings::values.yuzu_username =
            UsernameFromDisplayToken(ui->edit_token->text().toStdString());
        Settings::values.yuzu_token = TokenFromDisplayToken(ui->edit_token->text().toStdString());
    } else {
        QMessageBox::warning(
            this, tr("Token not verified"),
            tr("Token was not verified. The change to your token has not been saved."));
    }
}

void ConfigureWeb::RefreshTelemetryID() {
    const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
    ui->label_telemetry_id->setText(
        tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper()));
}

void ConfigureWeb::OnLoginChanged() {
    if (ui->edit_token->text().isEmpty()) {
        user_verified = true;
        // Empty = no icon
        ui->label_token_verified->setPixmap(QPixmap());
        ui->label_token_verified->setToolTip(QString());
    } else {
        user_verified = false;

        // Show an info icon if it's been changed, clearer than showing failure
        const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("info")).pixmap(16);
        ui->label_token_verified->setPixmap(pixmap);
        ui->label_token_verified->setToolTip(
            tr("Unverified, please click Verify before saving configuration", "Tooltip"));
    }
}

void ConfigureWeb::VerifyLogin() {
    ui->button_verify_login->setDisabled(true);
    ui->button_verify_login->setText(tr("Verifying..."));
    ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16));
    ui->label_token_verified->setToolTip(tr("Verifying..."));
    verify_watcher.setFuture(QtConcurrent::run(
        [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()),
         token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] {
            return Core::VerifyLogin(username, token);
        }));
}

void ConfigureWeb::OnLoginVerified() {
    ui->button_verify_login->setEnabled(true);
    ui->button_verify_login->setText(tr("Verify"));
    if (verify_watcher.result()) {
        user_verified = true;

        ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16));
        ui->label_token_verified->setToolTip(tr("Verified", "Tooltip"));
        ui->username->setText(
            QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
    } else {
        ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16));
        ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip"));
        ui->username->setText(tr("Unspecified"));
        QMessageBox::critical(this, tr("Verification failed"),
                              tr("Verification failed. Check that you have entered your token "
                                 "correctly, and that your internet connection is working."));
    }
}

void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) {
    ui->label_disable_info->setVisible(!enabled);
    ui->groupBoxWebConfig->setEnabled(enabled);
}


code:
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>
#include <QFutureWatcher>
#include <QWidget>

namespace Ui {
class ConfigureWeb;
}

class ConfigureWeb : public QWidget {
    Q_OBJECT

public:
    explicit ConfigureWeb(QWidget* parent = nullptr);
    ~ConfigureWeb() override;

    void ApplyConfiguration();
    void SetWebServiceConfigEnabled(bool enabled);

private:
    void changeEvent(QEvent* event) override;
    void RetranslateUI();

    void RefreshTelemetryID();
    void OnLoginChanged();
    void VerifyLogin();
    void OnLoginVerified();

    void SetConfiguration();

    bool user_verified = true;
    QFutureWatcher<bool> verify_watcher;

    std::unique_ptr<Ui::ConfigureWeb> ui;
};


code:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ConfigureWeb</class>
 <widget class="QWidget" name="ConfigureWeb">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>926</width>
    <height>561</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <property name="accessibleName">
   <string>Web</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <layout class="QVBoxLayout" name="verticalLayout_3">
     <item>
      <widget class="QGroupBox" name="groupBoxWebConfig">
       <property name="title">
        <string>yuzu Web Service</string>
       </property>
       <layout class="QVBoxLayout" name="verticalLayoutYuzuWebService">
        <item>
         <widget class="QLabel" name="web_credentials_disclaimer">
          <property name="text">
           <string>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</string>
          </property>
         </widget>
        </item>
        <item>
         <layout class="QGridLayout" name="gridLayoutYuzuUsername">
          <item row="2" column="3">
           <widget class="QPushButton" name="button_verify_login">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
              <horstretch>0</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
            </property>
            <property name="layoutDirection">
             <enum>Qt::RightToLeft</enum>
            </property>
            <property name="text">
             <string>Verify</string>
            </property>
           </widget>
          </item>
          <item row="2" column="0">
           <widget class="QLabel" name="web_signup_link">
            <property name="text">
             <string>Sign up</string>
            </property>
           </widget>
          </item>
          <item row="0" column="1" colspan="3">
           <widget class="QLabel" name="username"/>
          </item>
          <item row="1" column="0">
           <widget class="QLabel" name="label_token">
            <property name="text">
             <string>Token: </string>
            </property>
           </widget>
          </item>
          <item row="1" column="4">
           <widget class="QLabel" name="label_token_verified"/>
          </item>
          <item row="0" column="0">
           <widget class="QLabel" name="label_username">
            <property name="text">
             <string>Username: </string>
            </property>
           </widget>
          </item>
          <item row="1" column="1" colspan="3">
           <widget class="QLineEdit" name="edit_token">
            <property name="maxLength">
             <number>80</number>
            </property>
            <property name="echoMode">
             <enum>QLineEdit::Password</enum>
            </property>
           </widget>
          </item>
          <item row="2" column="1">
           <widget class="QLabel" name="web_token_info_link">
            <property name="text">
             <string>What is my token?</string>
            </property>
           </widget>
          </item>
          <item row="2" column="2">
           <spacer name="horizontalSpacer">
            <property name="orientation">
             <enum>Qt::Horizontal</enum>
            </property>
            <property name="sizeHint" stdset="0">
             <size>
              <width>40</width>
              <height>20</height>
             </size>
            </property>
           </spacer>
          </item>
         </layout>
        </item>
       </layout>
      </widget>
     </item>
     <item>
      <widget class="QLabel" name="label_disable_info">
       <property name="text">
        <string>Web Service configuration can only be changed when a public room isn't being hosted.</string>
       </property>
       <property name="wordWrap">
        <bool>true</bool>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QGroupBox" name="groupBox">
       <property name="title">
        <string>Telemetry</string>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_2">
        <item>
         <widget class="QCheckBox" name="toggle_telemetry">
          <property name="text">
           <string>Share anonymous usage data with the yuzu team</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QLabel" name="telemetry_learn_more">
          <property name="text">
           <string>Learn more</string>
          </property>
         </widget>
        </item>
        <item>
         <layout class="QGridLayout" name="gridLayoutTelemetryId">
          <item row="0" column="0">
           <widget class="QLabel" name="label_telemetry_id">
            <property name="text">
             <string>Telemetry ID:</string>
            </property>
           </widget>
          </item>
          <item row="0" column="1">
           <widget class="QPushButton" name="button_regenerate_telemetry_id">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
              <horstretch>0</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
            </property>
            <property name="layoutDirection">
             <enum>Qt::RightToLeft</enum>
            </property>
            <property name="text">
             <string>Regenerate</string>
            </property>
           </widget>
          </item>
         </layout>
        </item>
       </layout>
      </widget>
     </item>
    </layout>
   </item>
   <item>
    <widget class="QGroupBox" name="discord_group">
     <property name="title">
      <string>Discord Presence</string>
     </property>
     <layout class="QVBoxLayout" name="verticalLayout_21">
      <item>
       <widget class="QCheckBox" name="toggle_discordrpc">
        <property name="text">
         <string>Show Current Game in your Discord Status</string>
        </property>
       </widget>
      </item>
     </layout>
    </widget>
   </item>
   <item>
    <spacer name="verticalSpacer">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>


code:
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <fmt/format.h>

#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "frontend_common/config.h"
#include "yuzu/configuration/input_profiles.h"

namespace FS = Common::FS;

namespace {

bool ProfileExistsInFilesystem(std::string_view profile_name) {
    return FS::Exists(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input" /
                      fmt::format("{}.ini", profile_name));
}

bool IsINI(const std::filesystem::path& filename) {
    return filename.extension() == ".ini";
}

std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) {
    return filename.replace_extension();
}

} // namespace

InputProfiles::InputProfiles() {
    const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input";

    if (!FS::IsDir(input_profile_loc)) {
        return;
    }

    FS::IterateDirEntries(
        input_profile_loc,
        [this](const std::filesystem::path& full_path) {
            const auto filename = full_path.filename();
            const auto name_without_ext =
                Common::FS::PathToUTF8String(GetNameWithoutExtension(filename));

            if (IsINI(filename) && IsProfileNameValid(name_without_ext)) {
                map_profiles.insert_or_assign(
                    name_without_ext,
                    std::make_unique<QtConfig>(name_without_ext, Config::ConfigType::InputProfile));
            }

            return true;
        },
        FS::DirEntryFilter::File);
}

InputProfiles::~InputProfiles() = default;

std::vector<std::string> InputProfiles::GetInputProfileNames() {
    std::vector<std::string> profile_names;
    profile_names.reserve(map_profiles.size());

    auto it = map_profiles.cbegin();
    while (it != map_profiles.cend()) {
        const auto& [profile_name, config] = *it;
        if (!ProfileExistsInFilesystem(profile_name)) {
            it = map_profiles.erase(it);
            continue;
        }

        profile_names.push_back(profile_name);
        ++it;
    }

    std::stable_sort(profile_names.begin(), profile_names.end());

    return profile_names;
}

bool InputProfiles::IsProfileNameValid(std::string_view profile_name) {
    return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos;
}

bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t player_index) {
    if (ProfileExistsInMap(profile_name)) {
        return false;
    }

    map_profiles.insert_or_assign(
        profile_name, std::make_unique<QtConfig>(profile_name, Config::ConfigType::InputProfile));

    return SaveProfile(profile_name, player_index);
}

bool InputProfiles::DeleteProfile(const std::string& profile_name) {
    if (!ProfileExistsInMap(profile_name)) {
        return false;
    }

    if (!ProfileExistsInFilesystem(profile_name) ||
        FS::RemoveFile(map_profiles[profile_name]->GetConfigFilePath())) {
        map_profiles.erase(profile_name);
    }

    return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name);
}

bool InputProfiles::LoadProfile(const std::string& profile_name, std::size_t player_index) {
    if (!ProfileExistsInMap(profile_name)) {
        return false;
    }

    if (!ProfileExistsInFilesystem(profile_name)) {
        map_profiles.erase(profile_name);
        return false;
    }

    LOG_INFO(Config, "Loading input profile `{}`", profile_name);

    map_profiles[profile_name]->ReadQtControlPlayerValues(player_index);
    return true;
}

bool InputProfiles::SaveProfile(const std::string& profile_name, std::size_t player_index) {
    if (!ProfileExistsInMap(profile_name)) {
        return false;
    }

    map_profiles[profile_name]->SaveQtControlPlayerValues(player_index);
    return true;
}

bool InputProfiles::ProfileExistsInMap(const std::string& profile_name) const {
    return map_profiles.find(profile_name) != map_profiles.end();
}


code:
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <string>
#include <unordered_map>

#include "configuration/qt_config.h"

namespace Core {
class System;
}

class Config;

class InputProfiles {

public:
    explicit InputProfiles();
    virtual ~InputProfiles();

    std::vector<std::string> GetInputProfileNames();

    static bool IsProfileNameValid(std::string_view profile_name);

    bool CreateProfile(const std::string& profile_name, std::size_t player_index);
    bool DeleteProfile(const std::string& profile_name);
    bool LoadProfile(const std::string& profile_name, std::size_t player_index);
    bool SaveProfile(const std::string& profile_name, std::size_t player_index);

private:
    bool ProfileExistsInMap(const std::string& profile_name) const;

    std::unordered_map<std::string, std::unique_ptr<QtConfig>> map_profiles;
};


code:
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/logging/log.h"
#include "input_common/main.h"
#include "qt_config.h"
#include "uisettings.h"

const std::array<int, Settings::NativeButton::NumButtons> QtConfig::default_buttons = {
    Qt::Key_C,    Qt::Key_X, Qt::Key_V,    Qt::Key_Z,  Qt::Key_F,
    Qt::Key_G,    Qt::Key_Q, Qt::Key_E,    Qt::Key_R,  Qt::Key_T,
    Qt::Key_M,    Qt::Key_N, Qt::Key_Left, Qt::Key_Up, Qt::Key_Right,
    Qt::Key_Down, Qt::Key_Q, Qt::Key_E,    0,          0,
    Qt::Key_Q,    Qt::Key_E,
};

const std::array<int, Settings::NativeMotion::NumMotions> QtConfig::default_motions = {
    Qt::Key_7,
    Qt::Key_8,
};

const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> QtConfig::default_analogs{{
    {
        Qt::Key_W,
        Qt::Key_S,
        Qt::Key_A,
        Qt::Key_D,
    },
    {
        Qt::Key_I,
        Qt::Key_K,
        Qt::Key_J,
        Qt::Key_L,
    },
}};

const std::array<int, 2> QtConfig::default_stick_mod = {
    Qt::Key_Shift,
    0,
};

const std::array<int, 2> QtConfig::default_ringcon_analogs{{
    Qt::Key_A,
    Qt::Key_D,
}};

QtConfig::QtConfig(const std::string& config_name, const ConfigType config_type)
    : Config(config_type) {
    Initialize(config_name);
    if (config_type != ConfigType::InputProfile) {
        ReadQtValues();
        SaveQtValues();
    }
}

QtConfig::~QtConfig() {
    if (global) {
        QtConfig::SaveAllValues();
    }
}

void QtConfig::ReloadAllValues() {
    Reload();
    ReadQtValues();
    SaveQtValues();
}

void QtConfig::SaveAllValues() {
    SaveValues();
    SaveQtValues();
}

void QtConfig::ReadQtValues() {
    if (global) {
        ReadUIValues();
    }
    ReadQtControlValues();
}

void QtConfig::ReadQtPlayerValues(const std::size_t player_index) {
    std::string player_prefix;
    if (type != ConfigType::InputProfile) {
        player_prefix.append("player_").append(ToString(player_index)).append("_");
    }

    auto& player = Settings::values.players.GetValue()[player_index];
    if (IsCustomConfig()) {
        const auto profile_name =
            ReadStringSetting(std::string(player_prefix).append("profile_name"));
        if (profile_name.empty()) {
            // Use the global input config
            player = Settings::values.players.GetValue(true)[player_index];
            player.profile_name = "";
            return;
        }
    }

    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
        auto& player_buttons = player.buttons[i];

        player_buttons = ReadStringSetting(
            std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param);
        if (player_buttons.empty()) {
            player_buttons = default_param;
        }
    }

    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
            default_analogs[i][3], default_stick_mod[i], 0.5f);
        auto& player_analogs = player.analogs[i];

        player_analogs = ReadStringSetting(
            std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param);
        if (player_analogs.empty()) {
            player_analogs = default_param;
        }
    }

    for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
        const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
        auto& player_motions = player.motions[i];

        player_motions = ReadStringSetting(
            std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param);
        if (player_motions.empty()) {
            player_motions = default_param;
        }
    }
}

void QtConfig::ReadHidbusValues() {
    const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
        0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
    auto& ringcon_analogs = Settings::values.ringcon_analogs;

    ringcon_analogs = ReadStringSetting(std::string("ring_controller"), default_param);
    if (ringcon_analogs.empty()) {
        ringcon_analogs = default_param;
    }
}

void QtConfig::ReadDebugControlValues() {
    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
        auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];

        debug_pad_buttons = ReadStringSetting(
            std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), default_param);
        if (debug_pad_buttons.empty()) {
            debug_pad_buttons = default_param;
        }
    }

    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
            default_analogs[i][3], default_stick_mod[i], 0.5f);
        auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];

        debug_pad_analogs = ReadStringSetting(
            std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), default_param);
        if (debug_pad_analogs.empty()) {
            debug_pad_analogs = default_param;
        }
    }
}

void QtConfig::ReadQtControlValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));

    Settings::values.players.SetGlobal(!IsCustomConfig());
    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
        ReadQtPlayerValues(p);
    }
    if (IsCustomConfig()) {
        EndGroup();
        return;
    }
    ReadDebugControlValues();
    ReadHidbusValues();

    EndGroup();
}

void QtConfig::ReadPathValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));

    UISettings::values.roms_path = ReadStringSetting(std::string("romsPath"));
    UISettings::values.game_dir_deprecated =
        ReadStringSetting(std::string("gameListRootDir"), std::string("."));
    UISettings::values.game_dir_deprecated_deepscan =
        ReadBooleanSetting(std::string("gameListDeepScan"), std::make_optional(false));

    const int gamedirs_size = BeginArray(std::string("gamedirs"));
    for (int i = 0; i < gamedirs_size; ++i) {
        SetArrayIndex(i);
        UISettings::GameDir game_dir;
        game_dir.path = ReadStringSetting(std::string("path"));
        game_dir.deep_scan =
            ReadBooleanSetting(std::string("deep_scan"), std::make_optional(false));
        game_dir.expanded = ReadBooleanSetting(std::string("expanded"), std::make_optional(true));
        UISettings::values.game_dirs.append(game_dir);
    }
    EndArray();

    // Create NAND and SD card directories if empty, these are not removable through the UI,
    // also carries over old game list settings if present
    if (UISettings::values.game_dirs.empty()) {
        UISettings::GameDir game_dir;
        game_dir.path = std::string("SDMC");
        game_dir.expanded = true;
        UISettings::values.game_dirs.append(game_dir);
        game_dir.path = std::string("UserNAND");
        UISettings::values.game_dirs.append(game_dir);
        game_dir.path = std::string("SysNAND");
        UISettings::values.game_dirs.append(game_dir);
        if (UISettings::values.game_dir_deprecated != std::string(".")) {
            game_dir.path = UISettings::values.game_dir_deprecated;
            game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan;
            UISettings::values.game_dirs.append(game_dir);
        }
    }
    UISettings::values.recent_files =
        QString::fromStdString(ReadStringSetting(std::string("recentFiles")))
            .split(QStringLiteral(", "), Qt::SkipEmptyParts, Qt::CaseSensitive);

    ReadCategory(Settings::Category::Paths);

    EndGroup();
}

void QtConfig::ReadShortcutValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));

    for (const auto& [name, group, shortcut] : UISettings::default_hotkeys) {
        BeginGroup(group);
        BeginGroup(name);

        // No longer using ReadSetting for shortcut.second as it inaccurately returns a value of 1
        // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
        // a file dialog in windowed mode
        UISettings::values.shortcuts.push_back(
            {name,
             group,
             {ReadStringSetting(std::string("KeySeq"), shortcut.keyseq),
              ReadStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq),
              shortcut.context,
              ReadBooleanSetting(std::string("Repeat"), std::optional(shortcut.repeat))}});

        EndGroup(); // name
        EndGroup(); // group
    }

    EndGroup();
}

void QtConfig::ReadUIValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));

    UISettings::values.theme = ReadStringSetting(
        std::string("theme"),
        std::string(UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second));

    ReadUIGamelistValues();
    ReadUILayoutValues();
    ReadPathValues();
    ReadScreenshotValues();
    ReadShortcutValues();
    ReadMultiplayerValues();

    ReadCategory(Settings::Category::Ui);
    ReadCategory(Settings::Category::UiGeneral);

    EndGroup();
}

void QtConfig::ReadUIGamelistValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));

    ReadCategory(Settings::Category::UiGameList);

    const int favorites_size = BeginArray("favorites");
    for (int i = 0; i < favorites_size; i++) {
        SetArrayIndex(i);
        UISettings::values.favorited_ids.append(
            ReadUnsignedIntegerSetting(std::string("program_id")));
    }
    EndArray();

    EndGroup();
}

void QtConfig::ReadUILayoutValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));

    ReadCategory(Settings::Category::UiLayout);

    EndGroup();
}

void QtConfig::ReadMultiplayerValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::Multiplayer));

    ReadCategory(Settings::Category::Multiplayer);

    // Read ban list back
    int size = BeginArray(std::string("username_ban_list"));
    UISettings::values.multiplayer_ban_list.first.resize(size);
    for (int i = 0; i < size; ++i) {
        SetArrayIndex(i);
        UISettings::values.multiplayer_ban_list.first[i] =
            ReadStringSetting(std::string("username"), std::string(""));
    }
    EndArray();

    size = BeginArray(std::string("ip_ban_list"));
    UISettings::values.multiplayer_ban_list.second.resize(size);
    for (int i = 0; i < size; ++i) {
        UISettings::values.multiplayer_ban_list.second[i] =
            ReadStringSetting("username", std::string(""));
    }
    EndArray();

    EndGroup();
}

void QtConfig::SaveQtValues() {
    if (global) {
        LOG_DEBUG(Config, "Saving global Qt configuration values");
        SaveUIValues();
    } else {
        LOG_DEBUG(Config, "Saving Qt configuration values");
    }
    SaveQtControlValues();

    WriteToIni();
}

void QtConfig::SaveQtPlayerValues(const std::size_t player_index) {
    std::string player_prefix;
    if (type != ConfigType::InputProfile) {
        player_prefix = std::string("player_").append(ToString(player_index)).append("_");
    }

    const auto& player = Settings::values.players.GetValue()[player_index];
    if (IsCustomConfig() && player.profile_name.empty()) {
        // No custom profile selected
        return;
    }

    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
        WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
                           player.buttons[i], std::make_optional(default_param));
    }
    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
            default_analogs[i][3], default_stick_mod[i], 0.5f);
        WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
                           player.analogs[i], std::make_optional(default_param));
    }
    for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
        const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
        WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
                           player.motions[i], std::make_optional(default_param));
    }
}

void QtConfig::SaveDebugControlValues() {
    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
        WriteStringSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]),
                           Settings::values.debug_pad_buttons[i],
                           std::make_optional(default_param));
    }
    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
            default_analogs[i][3], default_stick_mod[i], 0.5f);
        WriteStringSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]),
                           Settings::values.debug_pad_analogs[i],
                           std::make_optional(default_param));
    }
}

void QtConfig::SaveHidbusValues() {
    const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
        0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
    WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
                       std::make_optional(default_param));
}

void QtConfig::SaveQtControlValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));

    Settings::values.players.SetGlobal(!IsCustomConfig());
    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
        SaveQtPlayerValues(p);
    }
    if (IsCustomConfig()) {
        EndGroup();
        return;
    }
    SaveDebugControlValues();
    SaveHidbusValues();

    EndGroup();
}

void QtConfig::SavePathValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));

    WriteCategory(Settings::Category::Paths);

    WriteStringSetting(std::string("romsPath"), UISettings::values.roms_path);
    BeginArray(std::string("gamedirs"));
    for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
        SetArrayIndex(i);
        const auto& game_dir = UISettings::values.game_dirs[i];
        WriteStringSetting(std::string("path"), game_dir.path);
        WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan,
                            std::make_optional(false));
        WriteBooleanSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true));
    }
    EndArray();

    WriteStringSetting(std::string("recentFiles"),
                       UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString());

    EndGroup();
}

void QtConfig::SaveShortcutValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));

    // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
    // However, their ordering must also be the same.
    for (std::size_t i = 0; i < UISettings::default_hotkeys.size(); i++) {
        const auto& [name, group, shortcut] = UISettings::values.shortcuts[i];
        const auto& default_hotkey = UISettings::default_hotkeys[i].shortcut;

        BeginGroup(group);
        BeginGroup(name);

        WriteStringSetting(std::string("KeySeq"), shortcut.keyseq,
                           std::make_optional(default_hotkey.keyseq));
        WriteStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq,
                           std::make_optional(default_hotkey.controller_keyseq));
        WriteIntegerSetting(std::string("Context"), shortcut.context,
                            std::make_optional(default_hotkey.context));
        WriteBooleanSetting(std::string("Repeat"), shortcut.repeat,
                            std::make_optional(default_hotkey.repeat));

        EndGroup(); // name
        EndGroup(); // group
    }

    EndGroup();
}

void QtConfig::SaveUIValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));

    WriteCategory(Settings::Category::Ui);
    WriteCategory(Settings::Category::UiGeneral);

    WriteStringSetting(
        std::string("theme"), UISettings::values.theme,
        std::make_optional(std::string(
            UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second)));

    SaveUIGamelistValues();
    SaveUILayoutValues();
    SavePathValues();
    SaveScreenshotValues();
    SaveShortcutValues();
    SaveMultiplayerValues();

    EndGroup();
}

void QtConfig::SaveUIGamelistValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));

    WriteCategory(Settings::Category::UiGameList);

    BeginArray(std::string("favorites"));
    for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
        SetArrayIndex(i);
        WriteIntegerSetting(std::string("program_id"), UISettings::values.favorited_ids[i]);
    }
    EndArray(); // favorites

    EndGroup();
}

void QtConfig::SaveUILayoutValues() {
    BeginGroup(Settings::TranslateCategory(Settings::Category::UiLayout));

    WriteCategory(Settings::Category::UiLayout);

    EndGroup();
}

void QtConfig::SaveMultiplayerValues() {
    BeginGroup(std::string("Multiplayer"));

    WriteCategory(Settings::Category::Multiplayer);

    // Write ban list
    BeginArray(std::string("username_ban_list"));
    for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) {
        SetArrayIndex(static_cast<int>(i));
        WriteStringSetting(std::string("username"),
                           UISettings::values.multiplayer_ban_list.first[i]);
    }
    EndArray(); // username_ban_list

    BeginArray(std::string("ip_ban_list"));
    for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) {
        SetArrayIndex(static_cast<int>(i));
        WriteStringSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]);
    }
    EndArray(); // ip_ban_list

    EndGroup();
}

std::vector<Settings::BasicSetting*>& QtConfig::FindRelevantList(Settings::Category category) {
    auto& map = Settings::values.linkage.by_category;
    if (map.contains(category)) {
        return Settings::values.linkage.by_category[category];
    }
    return UISettings::values.linkage.by_category[category];
}

void QtConfig::ReadQtControlPlayerValues(std::size_t player_index) {
    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));

    ReadPlayerValues(player_index);
    ReadQtPlayerValues(player_index);

    EndGroup();
}

void QtConfig::SaveQtControlPlayerValues(std::size_t player_index) {
    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));

    LOG_DEBUG(Config, "Saving players control configuration values");
    SavePlayerValues(player_index);
    SaveQtPlayerValues(player_index);

    EndGroup();

    WriteToIni();
}


code:
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <QMetaType>

#include "frontend_common/config.h"

class QtConfig final : public Config {
public:
    explicit QtConfig(const std::string& config_name = "qt-config",
                      ConfigType config_type = ConfigType::GlobalConfig);
    ~QtConfig() override;

    void ReloadAllValues() override;
    void SaveAllValues() override;

    void ReadQtControlPlayerValues(std::size_t player_index);
    void SaveQtControlPlayerValues(std::size_t player_index);

protected:
    void ReadQtValues();
    void ReadQtPlayerValues(std::size_t player_index);
    void ReadQtControlValues();
    void ReadHidbusValues() override;
    void ReadDebugControlValues() override;
    void ReadPathValues() override;
    void ReadShortcutValues() override;
    void ReadUIValues() override;
    void ReadUIGamelistValues() override;
    void ReadUILayoutValues() override;
    void ReadMultiplayerValues() override;

    void SaveQtValues();
    void SaveQtPlayerValues(std::size_t player_index);
    void SaveQtControlValues();
    void SaveHidbusValues() override;
    void SaveDebugControlValues() override;
    void SavePathValues() override;
    void SaveShortcutValues() override;
    void SaveUIValues() override;
    void SaveUIGamelistValues() override;
    void SaveUILayoutValues() override;
    void SaveMultiplayerValues() override;

    std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) override;

public:
    static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
    static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
    static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
    static const std::array<int, 2> default_stick_mod;
    static const std::array<int, 2> default_ringcon_analogs;
};