duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

controllersettingswindow.cpp (22313B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "controllersettingswindow.h"
      5 #include "controllerbindingwidgets.h"
      6 #include "controllerglobalsettingswidget.h"
      7 #include "hotkeysettingswidget.h"
      8 #include "qthost.h"
      9 
     10 #include "core/controller.h"
     11 #include "core/host.h"
     12 
     13 #include "util/ini_settings_interface.h"
     14 #include "util/input_manager.h"
     15 
     16 #include "common/assert.h"
     17 #include "common/file_system.h"
     18 
     19 #include <QtWidgets/QInputDialog>
     20 #include <QtWidgets/QMessageBox>
     21 #include <QtWidgets/QTextEdit>
     22 #include <array>
     23 
     24 static constexpr const std::array<char, 4> s_mtap_slot_names = {{'A', 'B', 'C', 'D'}};
     25 
     26 ControllerSettingsWindow::ControllerSettingsWindow(SettingsInterface* game_sif /* = nullptr */,
     27                                                    QWidget* parent /* = nullptr */)
     28   : QWidget(parent), m_editing_settings_interface(game_sif)
     29 {
     30   m_ui.setupUi(this);
     31 
     32   setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
     33 
     34   m_ui.settingsCategory->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
     35 
     36   connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this,
     37           &ControllerSettingsWindow::onCategoryCurrentRowChanged);
     38   connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &ControllerSettingsWindow::close);
     39 
     40   if (!game_sif)
     41   {
     42     refreshProfileList();
     43 
     44     m_ui.editProfileLayout->removeWidget(m_ui.copyGlobalSettings);
     45     delete m_ui.copyGlobalSettings;
     46     m_ui.copyGlobalSettings = nullptr;
     47 
     48     connect(m_ui.currentProfile, &QComboBox::currentIndexChanged, this,
     49             &ControllerSettingsWindow::onCurrentProfileChanged);
     50     connect(m_ui.newProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onNewProfileClicked);
     51     connect(m_ui.applyProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onApplyProfileClicked);
     52     connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onDeleteProfileClicked);
     53     connect(m_ui.restoreDefaults, &QPushButton::clicked, this, &ControllerSettingsWindow::onRestoreDefaultsClicked);
     54 
     55     connect(g_emu_thread, &EmuThread::onInputDevicesEnumerated, this,
     56             &ControllerSettingsWindow::onInputDevicesEnumerated);
     57     connect(g_emu_thread, &EmuThread::onInputDeviceConnected, this, &ControllerSettingsWindow::onInputDeviceConnected);
     58     connect(g_emu_thread, &EmuThread::onInputDeviceDisconnected, this,
     59             &ControllerSettingsWindow::onInputDeviceDisconnected);
     60     connect(g_emu_thread, &EmuThread::onVibrationMotorsEnumerated, this,
     61             &ControllerSettingsWindow::onVibrationMotorsEnumerated);
     62 
     63     // trigger a device enumeration to populate the device list
     64     g_emu_thread->enumerateInputDevices();
     65     g_emu_thread->enumerateVibrationMotors();
     66   }
     67   else
     68   {
     69     m_ui.editProfileLayout->removeWidget(m_ui.editProfileLabel);
     70     delete m_ui.editProfileLabel;
     71     m_ui.editProfileLabel = nullptr;
     72     m_ui.editProfileLayout->removeWidget(m_ui.currentProfile);
     73     delete m_ui.currentProfile;
     74     m_ui.currentProfile = nullptr;
     75     m_ui.editProfileLayout->removeWidget(m_ui.newProfile);
     76     delete m_ui.newProfile;
     77     m_ui.newProfile = nullptr;
     78     m_ui.editProfileLayout->removeWidget(m_ui.applyProfile);
     79     delete m_ui.applyProfile;
     80     m_ui.applyProfile = nullptr;
     81     m_ui.editProfileLayout->removeWidget(m_ui.deleteProfile);
     82     delete m_ui.deleteProfile;
     83     m_ui.deleteProfile = nullptr;
     84 
     85     connect(m_ui.copyGlobalSettings, &QPushButton::clicked, this,
     86             &ControllerSettingsWindow::onCopyGlobalSettingsClicked);
     87     connect(m_ui.restoreDefaults, &QPushButton::clicked, this,
     88             &ControllerSettingsWindow::onRestoreDefaultsForGameClicked);
     89   }
     90 
     91   createWidgets();
     92 }
     93 
     94 ControllerSettingsWindow::~ControllerSettingsWindow() = default;
     95 
     96 void ControllerSettingsWindow::editControllerSettingsForGame(QWidget* parent, SettingsInterface* sif)
     97 {
     98   ControllerSettingsWindow* dlg = new ControllerSettingsWindow(sif, parent);
     99   dlg->setWindowFlag(Qt::Window);
    100   dlg->setAttribute(Qt::WA_DeleteOnClose);
    101   dlg->setWindowModality(Qt::WindowModality::WindowModal);
    102   dlg->setWindowTitle(parent->windowTitle());
    103   dlg->setWindowIcon(parent->windowIcon());
    104   dlg->show();
    105 }
    106 
    107 int ControllerSettingsWindow::getHotkeyCategoryIndex() const
    108 {
    109   const std::array<bool, 2> mtap_enabled = getEnabledMultitaps();
    110   return 1 + (mtap_enabled[0] ? 4 : 1) + (mtap_enabled[1] ? 4 : 1);
    111 }
    112 
    113 ControllerSettingsWindow::Category ControllerSettingsWindow::getCurrentCategory() const
    114 {
    115   const int index = m_ui.settingsCategory->currentRow();
    116   if (index == 0)
    117     return Category::GlobalSettings;
    118   else if (index >= getHotkeyCategoryIndex())
    119     return Category::HotkeySettings;
    120   else
    121     return Category::FirstControllerSettings;
    122 }
    123 
    124 void ControllerSettingsWindow::setCategory(Category category)
    125 {
    126   switch (category)
    127   {
    128     case Category::GlobalSettings:
    129       m_ui.settingsCategory->setCurrentRow(0);
    130       break;
    131 
    132     case Category::FirstControllerSettings:
    133       m_ui.settingsCategory->setCurrentRow(1);
    134       break;
    135 
    136     case Category::HotkeySettings:
    137       m_ui.settingsCategory->setCurrentRow(getHotkeyCategoryIndex());
    138       break;
    139 
    140     default:
    141       break;
    142   }
    143 }
    144 
    145 void ControllerSettingsWindow::onCategoryCurrentRowChanged(int row)
    146 {
    147   m_ui.settingsContainer->setCurrentIndex(row);
    148 }
    149 
    150 void ControllerSettingsWindow::onCurrentProfileChanged(int index)
    151 {
    152   std::string profile_name;
    153   if (index > 0)
    154     profile_name = m_ui.currentProfile->itemText(index).toStdString();
    155 
    156   switchProfile(profile_name);
    157 }
    158 
    159 void ControllerSettingsWindow::onNewProfileClicked()
    160 {
    161   const std::string profile_name =
    162     QInputDialog::getText(this, tr("Create Input Profile"), tr("Enter the name for the new input profile:"))
    163       .toStdString();
    164   if (profile_name.empty())
    165     return;
    166 
    167   std::string profile_path = System::GetInputProfilePath(profile_name);
    168   if (FileSystem::FileExists(profile_path.c_str()))
    169   {
    170     QMessageBox::critical(this, tr("Error"),
    171                           tr("A profile with the name '%1' already exists.").arg(QString::fromStdString(profile_name)));
    172     return;
    173   }
    174 
    175   const int res = QMessageBox::question(this, tr("Create Input Profile"),
    176                                         tr("Do you want to copy all bindings from the currently-selected profile to "
    177                                            "the new profile? Selecting No will create a completely empty profile."),
    178                                         QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
    179   if (res == QMessageBox::Cancel)
    180     return;
    181 
    182   INISettingsInterface temp_si(std::move(profile_path));
    183   if (res == QMessageBox::Yes)
    184   {
    185     // copy from global or the current profile
    186     if (!m_editing_settings_interface)
    187     {
    188       const int hkres = QMessageBox::question(
    189         this, tr("Create Input Profile"),
    190         tr("Do you want to copy the current hotkey bindings from global settings to the new input profile?"),
    191         QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
    192       if (hkres == QMessageBox::Cancel)
    193         return;
    194 
    195       const bool copy_hotkey_bindings = (hkres == QMessageBox::Yes);
    196       if (copy_hotkey_bindings)
    197         temp_si.SetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", true);
    198 
    199       // from global
    200       auto lock = Host::GetSettingsLock();
    201       InputManager::CopyConfiguration(&temp_si, *Host::Internal::GetBaseSettingsLayer(), true, true,
    202                                       copy_hotkey_bindings);
    203     }
    204     else
    205     {
    206       // from profile
    207       const bool copy_hotkey_bindings =
    208         m_editing_settings_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false);
    209       temp_si.SetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", copy_hotkey_bindings);
    210       InputManager::CopyConfiguration(&temp_si, *m_editing_settings_interface, true, true, copy_hotkey_bindings);
    211     }
    212   }
    213 
    214   if (!temp_si.Save())
    215   {
    216     QMessageBox::critical(
    217       this, tr("Error"),
    218       tr("Failed to save the new profile to '%1'.").arg(QString::fromStdString(temp_si.GetFileName())));
    219     return;
    220   }
    221 
    222   refreshProfileList();
    223   switchProfile(profile_name);
    224 }
    225 
    226 void ControllerSettingsWindow::onApplyProfileClicked()
    227 {
    228   if (QMessageBox::question(this, tr("Load Input Profile"),
    229                             tr("Are you sure you want to load the input profile named '%1'?\n\n"
    230                                "All current global bindings will be removed, and the profile bindings loaded.\n\n"
    231                                "You cannot undo this action.")
    232                               .arg(m_profile_name)) != QMessageBox::Yes)
    233   {
    234     return;
    235   }
    236 
    237   {
    238     const bool copy_hotkey_bindings =
    239       m_editing_settings_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false);
    240     auto lock = Host::GetSettingsLock();
    241     InputManager::CopyConfiguration(Host::Internal::GetBaseSettingsLayer(), *m_editing_settings_interface, true, true,
    242                                     copy_hotkey_bindings);
    243     QtHost::QueueSettingsSave();
    244   }
    245   g_emu_thread->applySettings();
    246 
    247   // make it visible
    248   switchProfile({});
    249 }
    250 
    251 void ControllerSettingsWindow::onDeleteProfileClicked()
    252 {
    253   if (QMessageBox::question(this, tr("Delete Input Profile"),
    254                             tr("Are you sure you want to delete the input profile named '%1'?\n\n"
    255                                "You cannot undo this action.")
    256                               .arg(m_profile_name)) != QMessageBox::Yes)
    257   {
    258     return;
    259   }
    260 
    261   std::string profile_path(System::GetInputProfilePath(m_profile_name.toStdString()));
    262   if (!FileSystem::DeleteFile(profile_path.c_str()))
    263   {
    264     QMessageBox::critical(this, tr("Error"), tr("Failed to delete '%1'.").arg(QString::fromStdString(profile_path)));
    265     return;
    266   }
    267 
    268   // switch back to global
    269   refreshProfileList();
    270   switchProfile({});
    271 }
    272 
    273 void ControllerSettingsWindow::onRestoreDefaultsClicked()
    274 {
    275   if (QMessageBox::question(
    276         this, tr("Restore Defaults"),
    277         tr("Are you sure you want to restore the default controller configuration?\n\n"
    278            "All shared bindings and configuration will be lost, but your input profiles will remain.\n\n"
    279            "You cannot undo this action.")) != QMessageBox::Yes)
    280   {
    281     return;
    282   }
    283 
    284   // actually restore it
    285   g_emu_thread->setDefaultSettings(false, true);
    286 
    287   // reload all settings
    288   switchProfile({});
    289 }
    290 
    291 void ControllerSettingsWindow::onCopyGlobalSettingsClicked()
    292 {
    293   DebugAssert(isEditingGameSettings());
    294 
    295   {
    296     const auto lock = Host::GetSettingsLock();
    297     InputManager::CopyConfiguration(m_editing_settings_interface, *Host::Internal::GetBaseSettingsLayer(), true, true,
    298                                     false);
    299   }
    300 
    301   m_editing_settings_interface->Save();
    302   g_emu_thread->reloadGameSettings();
    303   createWidgets();
    304 
    305   QMessageBox::information(QtUtils::GetRootWidget(this), tr("DuckStation Controller Settings"),
    306                            tr("Per-game controller configuration reset to global settings."));
    307 }
    308 
    309 void ControllerSettingsWindow::onRestoreDefaultsForGameClicked()
    310 {
    311   DebugAssert(isEditingGameSettings());
    312   Settings::SetDefaultControllerConfig(*m_editing_settings_interface);
    313   m_editing_settings_interface->Save();
    314   g_emu_thread->reloadGameSettings();
    315   createWidgets();
    316 
    317   QMessageBox::information(QtUtils::GetRootWidget(this), tr("DuckStation Controller Settings"),
    318                            tr("Per-game controller configuration reset to default settings."));
    319 }
    320 
    321 void ControllerSettingsWindow::onInputDevicesEnumerated(const std::vector<std::pair<std::string, std::string>>& devices)
    322 {
    323   m_device_list = devices;
    324   for (const auto& [device_name, display_name] : m_device_list)
    325     m_global_settings->addDeviceToList(QString::fromStdString(device_name), QString::fromStdString(display_name));
    326 }
    327 
    328 void ControllerSettingsWindow::onInputDeviceConnected(const std::string& identifier, const std::string& device_name)
    329 {
    330   m_device_list.emplace_back(identifier, device_name);
    331   m_global_settings->addDeviceToList(QString::fromStdString(identifier), QString::fromStdString(device_name));
    332   g_emu_thread->enumerateVibrationMotors();
    333 }
    334 
    335 void ControllerSettingsWindow::onInputDeviceDisconnected(const std::string& identifier)
    336 {
    337   for (auto iter = m_device_list.begin(); iter != m_device_list.end(); ++iter)
    338   {
    339     if (iter->first == identifier)
    340     {
    341       m_device_list.erase(iter);
    342       break;
    343     }
    344   }
    345 
    346   m_global_settings->removeDeviceFromList(QString::fromStdString(identifier));
    347   g_emu_thread->enumerateVibrationMotors();
    348 }
    349 
    350 void ControllerSettingsWindow::onVibrationMotorsEnumerated(const QList<InputBindingKey>& motors)
    351 {
    352   m_vibration_motors.clear();
    353   m_vibration_motors.reserve(motors.size());
    354 
    355   for (const InputBindingKey key : motors)
    356   {
    357     const std::string key_str(InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type::Motor, key));
    358     if (!key_str.empty())
    359       m_vibration_motors.push_back(QString::fromStdString(key_str));
    360   }
    361 }
    362 
    363 bool ControllerSettingsWindow::getBoolValue(const char* section, const char* key, bool default_value) const
    364 {
    365   if (m_editing_settings_interface)
    366     return m_editing_settings_interface->GetBoolValue(section, key, default_value);
    367   else
    368     return Host::GetBaseBoolSettingValue(section, key, default_value);
    369 }
    370 
    371 s32 ControllerSettingsWindow::getIntValue(const char* section, const char* key, s32 default_value) const
    372 {
    373   if (m_editing_settings_interface)
    374     return m_editing_settings_interface->GetIntValue(section, key, default_value);
    375   else
    376     return Host::GetBaseIntSettingValue(section, key, default_value);
    377 }
    378 
    379 std::string ControllerSettingsWindow::getStringValue(const char* section, const char* key,
    380                                                      const char* default_value) const
    381 {
    382   std::string value;
    383   if (m_editing_settings_interface)
    384     value = m_editing_settings_interface->GetStringValue(section, key, default_value);
    385   else
    386     value = Host::GetBaseStringSettingValue(section, key, default_value);
    387   return value;
    388 }
    389 
    390 void ControllerSettingsWindow::setBoolValue(const char* section, const char* key, bool value)
    391 {
    392   if (m_editing_settings_interface)
    393   {
    394     m_editing_settings_interface->SetBoolValue(section, key, value);
    395     saveAndReloadGameSettings();
    396   }
    397   else
    398   {
    399     Host::SetBaseBoolSettingValue(section, key, value);
    400     Host::CommitBaseSettingChanges();
    401     g_emu_thread->applySettings();
    402   }
    403 }
    404 
    405 void ControllerSettingsWindow::setIntValue(const char* section, const char* key, s32 value)
    406 {
    407   if (m_editing_settings_interface)
    408   {
    409     m_editing_settings_interface->SetIntValue(section, key, value);
    410     saveAndReloadGameSettings();
    411   }
    412   else
    413   {
    414     Host::SetBaseIntSettingValue(section, key, value);
    415     Host::CommitBaseSettingChanges();
    416     g_emu_thread->applySettings();
    417   }
    418 }
    419 
    420 void ControllerSettingsWindow::setStringValue(const char* section, const char* key, const char* value)
    421 {
    422   if (m_editing_settings_interface)
    423   {
    424     m_editing_settings_interface->SetStringValue(section, key, value);
    425     saveAndReloadGameSettings();
    426   }
    427   else
    428   {
    429     Host::SetBaseStringSettingValue(section, key, value);
    430     Host::CommitBaseSettingChanges();
    431     g_emu_thread->applySettings();
    432   }
    433 }
    434 
    435 void ControllerSettingsWindow::saveAndReloadGameSettings()
    436 {
    437   DebugAssert(m_editing_settings_interface);
    438   QtHost::SaveGameSettings(m_editing_settings_interface, false);
    439   g_emu_thread->reloadGameSettings(false);
    440 }
    441 
    442 void ControllerSettingsWindow::clearSettingValue(const char* section, const char* key)
    443 {
    444   if (m_editing_settings_interface)
    445   {
    446     m_editing_settings_interface->DeleteValue(section, key);
    447     m_editing_settings_interface->Save();
    448     g_emu_thread->reloadGameSettings();
    449   }
    450   else
    451   {
    452     Host::DeleteBaseSettingValue(section, key);
    453     Host::CommitBaseSettingChanges();
    454     g_emu_thread->applySettings();
    455   }
    456 }
    457 
    458 void ControllerSettingsWindow::createWidgets()
    459 {
    460   QSignalBlocker sb(m_ui.settingsContainer);
    461   QSignalBlocker sb2(m_ui.settingsCategory);
    462 
    463   while (m_ui.settingsContainer->count() > 0)
    464   {
    465     QWidget* widget = m_ui.settingsContainer->widget(m_ui.settingsContainer->count() - 1);
    466     m_ui.settingsContainer->removeWidget(widget);
    467     widget->deleteLater();
    468   }
    469 
    470   m_ui.settingsCategory->clear();
    471 
    472   m_global_settings = nullptr;
    473   m_hotkey_settings = nullptr;
    474 
    475   {
    476     // global settings
    477     QListWidgetItem* item = new QListWidgetItem();
    478     item->setText(tr("Global Settings"));
    479     item->setIcon(QIcon::fromTheme(QStringLiteral("settings-3-line")));
    480     m_ui.settingsCategory->addItem(item);
    481     m_ui.settingsCategory->setCurrentRow(0);
    482     m_global_settings = new ControllerGlobalSettingsWidget(m_ui.settingsContainer, this);
    483     m_ui.settingsContainer->addWidget(m_global_settings);
    484     connect(m_global_settings, &ControllerGlobalSettingsWidget::bindingSetupChanged, this,
    485             &ControllerSettingsWindow::createWidgets);
    486     for (const auto& [identifier, device_name] : m_device_list)
    487       m_global_settings->addDeviceToList(QString::fromStdString(identifier), QString::fromStdString(device_name));
    488   }
    489 
    490   // load mtap settings
    491   const std::array<bool, 2> mtap_enabled = getEnabledMultitaps();
    492 
    493   // we reorder things a little to make it look less silly for mtap
    494   static constexpr const std::array<u32, MAX_PORTS> mtap_port_order = {{0, 2, 3, 4, 1, 5, 6, 7}};
    495 
    496   // create the ports
    497   for (u32 global_slot : mtap_port_order)
    498   {
    499     const bool is_mtap_port = Controller::PadIsMultitapSlot(global_slot);
    500     const auto [port, slot] = Controller::ConvertPadToPortAndSlot(global_slot);
    501     if (is_mtap_port && !mtap_enabled[port])
    502       continue;
    503 
    504     m_port_bindings[global_slot] = new ControllerBindingWidget(m_ui.settingsContainer, this, global_slot);
    505     m_ui.settingsContainer->addWidget(m_port_bindings[global_slot]);
    506 
    507     const QString display_name(QString::fromUtf8(m_port_bindings[global_slot]->getControllerInfo()->GetDisplayName()));
    508 
    509     QListWidgetItem* item = new QListWidgetItem();
    510     item->setText(mtap_enabled[port] ?
    511                     (tr("Controller Port %1%2\n%3").arg(port + 1).arg(s_mtap_slot_names[slot]).arg(display_name)) :
    512                     tr("Controller Port %1\n%2").arg(port + 1).arg(display_name));
    513     item->setIcon(m_port_bindings[global_slot]->getIcon());
    514     item->setData(Qt::UserRole, QVariant(global_slot));
    515     m_ui.settingsCategory->addItem(item);
    516   }
    517 
    518   // only add hotkeys if we're editing global settings
    519   if (!m_editing_settings_interface ||
    520       m_editing_settings_interface->GetBoolValue("ControllerPorts", "UseProfileHotkeyBindings", false))
    521   {
    522     QListWidgetItem* item = new QListWidgetItem();
    523     item->setText(tr("Hotkeys"));
    524     item->setIcon(QIcon::fromTheme(QStringLiteral("keyboard-line")));
    525     m_ui.settingsCategory->addItem(item);
    526     m_hotkey_settings = new HotkeySettingsWidget(m_ui.settingsContainer, this);
    527     m_ui.settingsContainer->addWidget(m_hotkey_settings);
    528   }
    529 
    530   if (!isEditingGameSettings())
    531   {
    532     m_ui.applyProfile->setEnabled(isEditingProfile());
    533     m_ui.deleteProfile->setEnabled(isEditingProfile());
    534     m_ui.restoreDefaults->setEnabled(isEditingGlobalSettings());
    535   }
    536 }
    537 
    538 void ControllerSettingsWindow::closeEvent(QCloseEvent* event)
    539 {
    540   QWidget::closeEvent(event);
    541   emit windowClosed();
    542 }
    543 
    544 void ControllerSettingsWindow::updateListDescription(u32 global_slot, ControllerBindingWidget* widget)
    545 {
    546   for (int i = 0; i < m_ui.settingsCategory->count(); i++)
    547   {
    548     QListWidgetItem* item = m_ui.settingsCategory->item(i);
    549     const QVariant item_data(item->data(Qt::UserRole));
    550     bool is_ok;
    551     if (item_data.toUInt(&is_ok) == global_slot && is_ok)
    552     {
    553       const std::array<bool, 2> mtap_enabled = getEnabledMultitaps();
    554       const auto [port, slot] = Controller::ConvertPadToPortAndSlot(global_slot);
    555 
    556       const QString display_name = QString::fromUtf8(widget->getControllerInfo()->GetDisplayName());
    557 
    558       item->setText(mtap_enabled[port] ?
    559                       (tr("Controller Port %1%2\n%3").arg(port + 1).arg(s_mtap_slot_names[slot]).arg(display_name)) :
    560                       tr("Controller Port %1\n%2").arg(port + 1).arg(display_name));
    561       item->setIcon(widget->getIcon());
    562       break;
    563     }
    564   }
    565 }
    566 
    567 std::array<bool, 2> ControllerSettingsWindow::getEnabledMultitaps() const
    568 {
    569   const MultitapMode mtap_mode =
    570     Settings::ParseMultitapModeName(
    571       getStringValue("ControllerPorts", "MultitapMode", Settings::GetMultitapModeName(Settings::DEFAULT_MULTITAP_MODE))
    572         .c_str())
    573       .value_or(Settings::DEFAULT_MULTITAP_MODE);
    574   return {{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts),
    575            (mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}};
    576 }
    577 void ControllerSettingsWindow::refreshProfileList()
    578 {
    579   const std::vector<std::string> names(InputManager::GetInputProfileNames());
    580 
    581   QSignalBlocker sb(m_ui.currentProfile);
    582   m_ui.currentProfile->clear();
    583   m_ui.currentProfile->addItem(tr("Shared"));
    584   if (isEditingGlobalSettings())
    585     m_ui.currentProfile->setCurrentIndex(0);
    586 
    587   for (const std::string& name : names)
    588   {
    589     const QString qname(QString::fromStdString(name));
    590     m_ui.currentProfile->addItem(qname);
    591     if (qname == m_profile_name)
    592       m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->count() - 1);
    593   }
    594 }
    595 
    596 void ControllerSettingsWindow::switchProfile(const std::string_view name)
    597 {
    598   QSignalBlocker sb(m_ui.currentProfile);
    599 
    600   if (!name.empty())
    601   {
    602     const QString name_qstr = QtUtils::StringViewToQString(name);
    603 
    604     std::string path = System::GetInputProfilePath(name);
    605     if (!FileSystem::FileExists(path.c_str()))
    606     {
    607       QMessageBox::critical(this, tr("Error"), tr("The input profile named '%1' cannot be found.").arg(name_qstr));
    608       return;
    609     }
    610 
    611     std::unique_ptr<INISettingsInterface> sif = std::make_unique<INISettingsInterface>(std::move(path));
    612     sif->Load();
    613 
    614     m_profile_settings_interface = std::move(sif);
    615     m_editing_settings_interface = m_profile_settings_interface.get();
    616     m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->findText(name_qstr));
    617     m_profile_name = name_qstr;
    618   }
    619   else
    620   {
    621     m_profile_settings_interface.reset();
    622     m_editing_settings_interface = nullptr;
    623     m_ui.currentProfile->setCurrentIndex(0);
    624     m_profile_name = QString();
    625   }
    626 
    627   createWidgets();
    628 }