configure_input_player.cpp 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168
  1. // Copyright 2016 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <algorithm>
  5. #include <memory>
  6. #include <utility>
  7. #include <QGridLayout>
  8. #include <QInputDialog>
  9. #include <QKeyEvent>
  10. #include <QMenu>
  11. #include <QMessageBox>
  12. #include <QTimer>
  13. #include "common/param_package.h"
  14. #include "core/core.h"
  15. #include "core/hle/service/hid/controllers/npad.h"
  16. #include "core/hle/service/hid/hid.h"
  17. #include "core/hle/service/sm/sm.h"
  18. #include "input_common/gcadapter/gc_poller.h"
  19. #include "input_common/main.h"
  20. #include "input_common/udp/udp.h"
  21. #include "ui_configure_input_player.h"
  22. #include "yuzu/configuration/config.h"
  23. #include "yuzu/configuration/configure_input_player.h"
  24. #include "yuzu/configuration/input_profiles.h"
  25. #include "yuzu/util/limitable_input_dialog.h"
  26. constexpr std::size_t HANDHELD_INDEX = 8;
  27. const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
  28. ConfigureInputPlayer::analog_sub_buttons{{
  29. "up",
  30. "down",
  31. "left",
  32. "right",
  33. }};
  34. namespace {
  35. void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
  36. bool connected) {
  37. Core::System& system{Core::System::GetInstance()};
  38. if (!system.IsPoweredOn()) {
  39. return;
  40. }
  41. Service::SM::ServiceManager& sm = system.ServiceManager();
  42. auto& npad =
  43. sm.GetService<Service::HID::Hid>("hid")
  44. ->GetAppletResource()
  45. ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
  46. npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
  47. }
  48. /// Maps the controller type combobox index to Controller Type enum
  49. constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) {
  50. switch (index) {
  51. case 0:
  52. default:
  53. return Settings::ControllerType::ProController;
  54. case 1:
  55. return Settings::ControllerType::DualJoyconDetached;
  56. case 2:
  57. return Settings::ControllerType::LeftJoycon;
  58. case 3:
  59. return Settings::ControllerType::RightJoycon;
  60. case 4:
  61. return Settings::ControllerType::Handheld;
  62. }
  63. }
  64. /// Maps the Controller Type enum to controller type combobox index
  65. constexpr int GetIndexFromControllerType(Settings::ControllerType type) {
  66. switch (type) {
  67. case Settings::ControllerType::ProController:
  68. default:
  69. return 0;
  70. case Settings::ControllerType::DualJoyconDetached:
  71. return 1;
  72. case Settings::ControllerType::LeftJoycon:
  73. return 2;
  74. case Settings::ControllerType::RightJoycon:
  75. return 3;
  76. case Settings::ControllerType::Handheld:
  77. return 4;
  78. }
  79. }
  80. QString GetKeyName(int key_code) {
  81. switch (key_code) {
  82. case Qt::LeftButton:
  83. return QObject::tr("Click 0");
  84. case Qt::RightButton:
  85. return QObject::tr("Click 1");
  86. case Qt::MiddleButton:
  87. return QObject::tr("Click 2");
  88. case Qt::BackButton:
  89. return QObject::tr("Click 3");
  90. case Qt::ForwardButton:
  91. return QObject::tr("Click 4");
  92. case Qt::Key_Shift:
  93. return QObject::tr("Shift");
  94. case Qt::Key_Control:
  95. return QObject::tr("Ctrl");
  96. case Qt::Key_Alt:
  97. return QObject::tr("Alt");
  98. case Qt::Key_Meta:
  99. return {};
  100. default:
  101. return QKeySequence(key_code).toString();
  102. }
  103. }
  104. void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
  105. const std::string& button_name) {
  106. // The poller returned a complete axis, so set all the buttons
  107. if (input_param.Has("axis_x") && input_param.Has("axis_y")) {
  108. analog_param = input_param;
  109. return;
  110. }
  111. // Check if the current configuration has either no engine or an axis binding.
  112. // Clears out the old binding and adds one with analog_from_button.
  113. if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) {
  114. analog_param = {
  115. {"engine", "analog_from_button"},
  116. };
  117. }
  118. analog_param.Set(button_name, input_param.Serialize());
  119. }
  120. QString ButtonToText(const Common::ParamPackage& param) {
  121. if (!param.Has("engine")) {
  122. return QObject::tr("[not set]");
  123. }
  124. if (param.Get("engine", "") == "keyboard") {
  125. return GetKeyName(param.Get("code", 0));
  126. }
  127. if (param.Get("engine", "") == "gcpad") {
  128. if (param.Has("axis")) {
  129. const QString axis_str = QString::fromStdString(param.Get("axis", ""));
  130. const QString direction_str = QString::fromStdString(param.Get("direction", ""));
  131. return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str);
  132. }
  133. if (param.Has("button")) {
  134. const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
  135. return QObject::tr("GC Button %1").arg(button_str);
  136. }
  137. return GetKeyName(param.Get("code", 0));
  138. }
  139. if (param.Get("engine", "") == "cemuhookudp") {
  140. if (param.Has("pad_index")) {
  141. const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
  142. return QObject::tr("Motion %1").arg(motion_str);
  143. }
  144. return GetKeyName(param.Get("code", 0));
  145. }
  146. if (param.Get("engine", "") == "sdl") {
  147. if (param.Has("hat")) {
  148. const QString hat_str = QString::fromStdString(param.Get("hat", ""));
  149. const QString direction_str = QString::fromStdString(param.Get("direction", ""));
  150. return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
  151. }
  152. if (param.Has("axis")) {
  153. const QString axis_str = QString::fromStdString(param.Get("axis", ""));
  154. const QString direction_str = QString::fromStdString(param.Get("direction", ""));
  155. return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
  156. }
  157. if (param.Has("button")) {
  158. const QString button_str = QString::fromStdString(param.Get("button", ""));
  159. return QObject::tr("Button %1").arg(button_str);
  160. }
  161. return {};
  162. }
  163. return QObject::tr("[unknown]");
  164. }
  165. QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
  166. if (!param.Has("engine")) {
  167. return QObject::tr("[not set]");
  168. }
  169. if (param.Get("engine", "") == "analog_from_button") {
  170. return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
  171. }
  172. if (param.Get("engine", "") == "sdl") {
  173. if (dir == "modifier") {
  174. return QObject::tr("[unused]");
  175. }
  176. if (dir == "left" || dir == "right") {
  177. const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
  178. return QObject::tr("Axis %1").arg(axis_x_str);
  179. }
  180. if (dir == "up" || dir == "down") {
  181. const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
  182. return QObject::tr("Axis %1").arg(axis_y_str);
  183. }
  184. return {};
  185. }
  186. if (param.Get("engine", "") == "gcpad") {
  187. if (dir == "modifier") {
  188. return QObject::tr("[unused]");
  189. }
  190. if (dir == "left" || dir == "right") {
  191. const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
  192. return QObject::tr("GC Axis %1").arg(axis_x_str);
  193. }
  194. if (dir == "up" || dir == "down") {
  195. const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
  196. return QObject::tr("GC Axis %1").arg(axis_y_str);
  197. }
  198. return {};
  199. }
  200. return QObject::tr("[unknown]");
  201. }
  202. } // namespace
  203. ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index,
  204. QWidget* bottom_row,
  205. InputCommon::InputSubsystem* input_subsystem_,
  206. InputProfiles* profiles_, bool debug)
  207. : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
  208. debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_),
  209. timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()),
  210. bottom_row(bottom_row) {
  211. ui->setupUi(this);
  212. setFocusPolicy(Qt::ClickFocus);
  213. button_map = {
  214. ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY,
  215. ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR,
  216. ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus,
  217. ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown,
  218. ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot,
  219. };
  220. analog_map_buttons = {{
  221. {
  222. ui->buttonLStickUp,
  223. ui->buttonLStickDown,
  224. ui->buttonLStickLeft,
  225. ui->buttonLStickRight,
  226. },
  227. {
  228. ui->buttonRStickUp,
  229. ui->buttonRStickDown,
  230. ui->buttonRStickLeft,
  231. ui->buttonRStickRight,
  232. },
  233. }};
  234. motion_map = {
  235. ui->buttonMotionLeft,
  236. ui->buttonMotionRight,
  237. };
  238. analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone};
  239. analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone};
  240. analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup};
  241. analog_map_modifier_button = {ui->buttonLStickMod, ui->buttonRStickMod};
  242. analog_map_modifier_label = {ui->labelLStickModifierRange, ui->labelRStickModifierRange};
  243. analog_map_modifier_slider = {ui->sliderLStickModifierRange, ui->sliderRStickModifierRange};
  244. analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup};
  245. analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange};
  246. const auto ConfigureButtonClick = [&](QPushButton* button, Common::ParamPackage* param,
  247. int default_val, InputCommon::Polling::DeviceType type) {
  248. connect(button, &QPushButton::clicked, [=, this] {
  249. HandleClick(
  250. button,
  251. [=, this](Common::ParamPackage params) {
  252. // Workaround for ZL & ZR for analog triggers like on XBOX
  253. // controllers. Analog triggers (from controllers like the XBOX
  254. // controller) would not work due to a different range of their
  255. // signals (from 0 to 255 on analog triggers instead of -32768 to
  256. // 32768 on analog joysticks). The SDL driver misinterprets analog
  257. // triggers as analog joysticks.
  258. // TODO: reinterpret the signal range for analog triggers to map the
  259. // values correctly. This is required for the correct emulation of
  260. // the analog triggers of the GameCube controller.
  261. if (button == ui->buttonZL || button == ui->buttonZR) {
  262. params.Set("direction", "+");
  263. params.Set("threshold", "0.5");
  264. }
  265. *param = std::move(params);
  266. },
  267. type);
  268. });
  269. };
  270. for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
  271. auto* const button = button_map[button_id];
  272. if (button == nullptr) {
  273. continue;
  274. }
  275. ConfigureButtonClick(button_map[button_id], &buttons_param[button_id],
  276. Config::default_buttons[button_id],
  277. InputCommon::Polling::DeviceType::Button);
  278. button->setContextMenuPolicy(Qt::CustomContextMenu);
  279. connect(button, &QPushButton::customContextMenuRequested,
  280. [=, this](const QPoint& menu_location) {
  281. QMenu context_menu;
  282. context_menu.addAction(tr("Clear"), [&] {
  283. buttons_param[button_id].Clear();
  284. button_map[button_id]->setText(tr("[not set]"));
  285. });
  286. context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
  287. });
  288. }
  289. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  290. auto* const button = motion_map[motion_id];
  291. if (button == nullptr) {
  292. continue;
  293. }
  294. ConfigureButtonClick(motion_map[motion_id], &motions_param[motion_id],
  295. Config::default_motions[motion_id],
  296. InputCommon::Polling::DeviceType::Motion);
  297. button->setContextMenuPolicy(Qt::CustomContextMenu);
  298. connect(button, &QPushButton::customContextMenuRequested,
  299. [=, this](const QPoint& menu_location) {
  300. QMenu context_menu;
  301. context_menu.addAction(tr("Clear"), [&] {
  302. motions_param[motion_id].Clear();
  303. motion_map[motion_id]->setText(tr("[not set]"));
  304. });
  305. context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location));
  306. });
  307. }
  308. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  309. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  310. auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
  311. if (analog_button == nullptr) {
  312. continue;
  313. }
  314. connect(analog_button, &QPushButton::clicked, [=, this] {
  315. HandleClick(
  316. analog_map_buttons[analog_id][sub_button_id],
  317. [=, this](const Common::ParamPackage& params) {
  318. SetAnalogParam(params, analogs_param[analog_id],
  319. analog_sub_buttons[sub_button_id]);
  320. },
  321. InputCommon::Polling::DeviceType::AnalogPreferred);
  322. });
  323. analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
  324. connect(analog_button, &QPushButton::customContextMenuRequested,
  325. [=, this](const QPoint& menu_location) {
  326. QMenu context_menu;
  327. context_menu.addAction(tr("Clear"), [&] {
  328. analogs_param[analog_id].Clear();
  329. analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
  330. });
  331. context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
  332. menu_location));
  333. });
  334. }
  335. // Handle clicks for the modifier buttons as well.
  336. connect(analog_map_modifier_button[analog_id], &QPushButton::clicked, [=, this] {
  337. HandleClick(
  338. analog_map_modifier_button[analog_id],
  339. [=, this](const Common::ParamPackage& params) {
  340. analogs_param[analog_id].Set("modifier", params.Serialize());
  341. },
  342. InputCommon::Polling::DeviceType::Button);
  343. });
  344. analog_map_modifier_button[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu);
  345. connect(analog_map_modifier_button[analog_id], &QPushButton::customContextMenuRequested,
  346. [=, this](const QPoint& menu_location) {
  347. QMenu context_menu;
  348. context_menu.addAction(tr("Clear"), [&] {
  349. analogs_param[analog_id].Set("modifier", "");
  350. analog_map_modifier_button[analog_id]->setText(tr("[not set]"));
  351. });
  352. context_menu.exec(
  353. analog_map_modifier_button[analog_id]->mapToGlobal(menu_location));
  354. });
  355. connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged),
  356. [=, this] {
  357. const auto spinbox_value = analog_map_range_spinbox[analog_id]->value();
  358. analogs_param[analog_id].Set("range", spinbox_value / 100.0f);
  359. });
  360. connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] {
  361. const auto slider_value = analog_map_deadzone_slider[analog_id]->value();
  362. analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value));
  363. analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
  364. });
  365. connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] {
  366. const auto slider_value = analog_map_modifier_slider[analog_id]->value();
  367. analog_map_modifier_label[analog_id]->setText(
  368. tr("Modifier Range: %1%").arg(slider_value));
  369. analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
  370. });
  371. }
  372. // Player Connected checkbox
  373. connect(ui->groupConnectedController, &QGroupBox::toggled,
  374. [this](bool checked) { emit Connected(checked); });
  375. // Set up controller type. Only Player 1 can choose Handheld.
  376. ui->comboControllerType->clear();
  377. QStringList controller_types = {
  378. tr("Pro Controller"),
  379. tr("Dual Joycons"),
  380. tr("Left Joycon"),
  381. tr("Right Joycon"),
  382. };
  383. if (player_index == 0) {
  384. controller_types.append(tr("Handheld"));
  385. connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged),
  386. [this](int index) {
  387. emit HandheldStateChanged(GetControllerTypeFromIndex(index) ==
  388. Settings::ControllerType::Handheld);
  389. });
  390. }
  391. // The Debug Controller can only choose the Pro Controller.
  392. if (debug) {
  393. ui->buttonScreenshot->setEnabled(false);
  394. ui->buttonHome->setEnabled(false);
  395. ui->groupConnectedController->setCheckable(false);
  396. QStringList debug_controller_types = {
  397. tr("Pro Controller"),
  398. };
  399. ui->comboControllerType->addItems(debug_controller_types);
  400. } else {
  401. ui->comboControllerType->addItems(controller_types);
  402. }
  403. UpdateControllerIcon();
  404. UpdateControllerAvailableButtons();
  405. UpdateMotionButtons();
  406. connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
  407. UpdateControllerIcon();
  408. UpdateControllerAvailableButtons();
  409. UpdateMotionButtons();
  410. });
  411. connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
  412. &ConfigureInputPlayer::UpdateMappingWithDefaults);
  413. ui->comboDevices->setCurrentIndex(-1);
  414. ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
  415. connect(ui->buttonRefreshDevices, &QPushButton::clicked,
  416. [this] { emit RefreshInputDevices(); });
  417. timeout_timer->setSingleShot(true);
  418. connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
  419. connect(poll_timer.get(), &QTimer::timeout, [this] {
  420. Common::ParamPackage params;
  421. if (input_subsystem->GetGCButtons()->IsPolling()) {
  422. params = input_subsystem->GetGCButtons()->GetNextInput();
  423. if (params.Has("engine") && IsInputAcceptable(params)) {
  424. SetPollingResult(params, false);
  425. return;
  426. }
  427. }
  428. if (input_subsystem->GetGCAnalogs()->IsPolling()) {
  429. params = input_subsystem->GetGCAnalogs()->GetNextInput();
  430. if (params.Has("engine") && IsInputAcceptable(params)) {
  431. SetPollingResult(params, false);
  432. return;
  433. }
  434. }
  435. if (input_subsystem->GetUDPMotions()->IsPolling()) {
  436. params = input_subsystem->GetUDPMotions()->GetNextInput();
  437. if (params.Has("engine")) {
  438. SetPollingResult(params, false);
  439. return;
  440. }
  441. }
  442. for (auto& poller : device_pollers) {
  443. params = poller->GetNextInput();
  444. if (params.Has("engine") && IsInputAcceptable(params)) {
  445. SetPollingResult(params, false);
  446. return;
  447. }
  448. }
  449. });
  450. RefreshInputProfiles();
  451. connect(ui->buttonProfilesNew, &QPushButton::clicked, this,
  452. &ConfigureInputPlayer::CreateProfile);
  453. connect(ui->buttonProfilesDelete, &QPushButton::clicked, this,
  454. &ConfigureInputPlayer::DeleteProfile);
  455. connect(ui->comboProfiles, qOverload<int>(&QComboBox::activated), this,
  456. &ConfigureInputPlayer::LoadProfile);
  457. connect(ui->buttonProfilesSave, &QPushButton::clicked, this,
  458. &ConfigureInputPlayer::SaveProfile);
  459. LoadConfiguration();
  460. // TODO(wwylele): enable this when we actually emulate it
  461. ui->buttonHome->setEnabled(false);
  462. }
  463. ConfigureInputPlayer::~ConfigureInputPlayer() = default;
  464. void ConfigureInputPlayer::ApplyConfiguration() {
  465. auto& player = Settings::values.players[player_index];
  466. auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons;
  467. auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs;
  468. std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(),
  469. [](const Common::ParamPackage& param) { return param.Serialize(); });
  470. std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(),
  471. [](const Common::ParamPackage& param) { return param.Serialize(); });
  472. if (debug) {
  473. return;
  474. }
  475. auto& motions = player.motions;
  476. std::transform(motions_param.begin(), motions_param.end(), motions.begin(),
  477. [](const Common::ParamPackage& param) { return param.Serialize(); });
  478. player.controller_type =
  479. static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex());
  480. player.connected = ui->groupConnectedController->isChecked();
  481. // Player 2-8
  482. if (player_index != 0) {
  483. UpdateController(player.controller_type, player_index, player.connected);
  484. return;
  485. }
  486. // Player 1 and Handheld
  487. auto& handheld = Settings::values.players[HANDHELD_INDEX];
  488. // If Handheld is selected, copy all the settings from Player 1 to Handheld.
  489. if (player.controller_type == Settings::ControllerType::Handheld) {
  490. handheld = player;
  491. handheld.connected = ui->groupConnectedController->isChecked();
  492. player.connected = false; // Disconnect Player 1
  493. } else {
  494. player.connected = ui->groupConnectedController->isChecked();
  495. handheld.connected = false; // Disconnect Handheld
  496. }
  497. UpdateController(player.controller_type, player_index, player.connected);
  498. UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected);
  499. }
  500. void ConfigureInputPlayer::showEvent(QShowEvent* event) {
  501. if (bottom_row == nullptr) {
  502. return;
  503. }
  504. QWidget::showEvent(event);
  505. ui->main->addWidget(bottom_row);
  506. }
  507. void ConfigureInputPlayer::changeEvent(QEvent* event) {
  508. if (event->type() == QEvent::LanguageChange) {
  509. RetranslateUI();
  510. }
  511. QWidget::changeEvent(event);
  512. }
  513. void ConfigureInputPlayer::RetranslateUI() {
  514. ui->retranslateUi(this);
  515. UpdateUI();
  516. }
  517. void ConfigureInputPlayer::LoadConfiguration() {
  518. auto& player = Settings::values.players[player_index];
  519. if (debug) {
  520. std::transform(Settings::values.debug_pad_buttons.begin(),
  521. Settings::values.debug_pad_buttons.end(), buttons_param.begin(),
  522. [](const std::string& str) { return Common::ParamPackage(str); });
  523. std::transform(Settings::values.debug_pad_analogs.begin(),
  524. Settings::values.debug_pad_analogs.end(), analogs_param.begin(),
  525. [](const std::string& str) { return Common::ParamPackage(str); });
  526. } else {
  527. std::transform(player.buttons.begin(), player.buttons.end(), buttons_param.begin(),
  528. [](const std::string& str) { return Common::ParamPackage(str); });
  529. std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(),
  530. [](const std::string& str) { return Common::ParamPackage(str); });
  531. std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(),
  532. [](const std::string& str) { return Common::ParamPackage(str); });
  533. }
  534. UpdateUI();
  535. UpdateInputDeviceCombobox();
  536. if (debug) {
  537. return;
  538. }
  539. ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type));
  540. ui->groupConnectedController->setChecked(
  541. player.connected ||
  542. (player_index == 0 && Settings::values.players[HANDHELD_INDEX].connected));
  543. }
  544. void ConfigureInputPlayer::ConnectPlayer(bool connected) {
  545. ui->groupConnectedController->setChecked(connected);
  546. }
  547. void ConfigureInputPlayer::UpdateInputDeviceCombobox() {
  548. // Skip input device persistence if "Input Devices" is set to "Any".
  549. if (ui->comboDevices->currentIndex() == 0) {
  550. UpdateInputDevices();
  551. return;
  552. }
  553. // Find the first button that isn't empty.
  554. const auto button_param =
  555. std::find_if(buttons_param.begin(), buttons_param.end(),
  556. [](const Common::ParamPackage param) { return param.Has("engine"); });
  557. const bool buttons_empty = button_param == buttons_param.end();
  558. const auto current_engine = button_param->Get("engine", "");
  559. const auto current_guid = button_param->Get("guid", "");
  560. const auto current_port = button_param->Get("port", "");
  561. UpdateInputDevices();
  562. if (buttons_empty) {
  563. return;
  564. }
  565. const bool all_one_device =
  566. std::all_of(buttons_param.begin(), buttons_param.end(),
  567. [current_engine, current_guid, current_port](const Common::ParamPackage param) {
  568. return !param.Has("engine") || (param.Get("engine", "") == current_engine &&
  569. param.Get("guid", "") == current_guid &&
  570. param.Get("port", "") == current_port);
  571. });
  572. if (all_one_device) {
  573. const auto devices_it = std::find_if(
  574. input_devices.begin(), input_devices.end(),
  575. [current_engine, current_guid, current_port](const Common::ParamPackage param) {
  576. return param.Get("class", "") == current_engine &&
  577. param.Get("guid", "") == current_guid &&
  578. param.Get("port", "") == current_port;
  579. });
  580. const int device_index =
  581. devices_it != input_devices.end()
  582. ? static_cast<int>(std::distance(input_devices.begin(), devices_it))
  583. : 0;
  584. ui->comboDevices->setCurrentIndex(device_index);
  585. } else {
  586. ui->comboDevices->setCurrentIndex(0);
  587. }
  588. }
  589. void ConfigureInputPlayer::RestoreDefaults() {
  590. // Reset Buttons
  591. for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
  592. buttons_param[button_id] = Common::ParamPackage{
  593. InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
  594. }
  595. // Reset Analogs and Modifier Buttons
  596. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  597. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  598. Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
  599. Config::default_analogs[analog_id][sub_button_id])};
  600. SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
  601. }
  602. analogs_param[analog_id].Set(
  603. "modifier", InputCommon::GenerateKeyboardParam(Config::default_stick_mod[analog_id]));
  604. }
  605. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  606. motions_param[motion_id] = Common::ParamPackage{
  607. InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])};
  608. }
  609. UpdateUI();
  610. UpdateInputDeviceCombobox();
  611. ui->comboControllerType->setCurrentIndex(0);
  612. }
  613. void ConfigureInputPlayer::ClearAll() {
  614. for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
  615. const auto* const button = button_map[button_id];
  616. if (button == nullptr) {
  617. continue;
  618. }
  619. buttons_param[button_id].Clear();
  620. }
  621. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  622. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  623. const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
  624. if (analog_button == nullptr) {
  625. continue;
  626. }
  627. analogs_param[analog_id].Clear();
  628. }
  629. }
  630. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  631. const auto* const motion_button = motion_map[motion_id];
  632. if (motion_button == nullptr) {
  633. continue;
  634. }
  635. motions_param[motion_id].Clear();
  636. }
  637. UpdateUI();
  638. UpdateInputDevices();
  639. }
  640. void ConfigureInputPlayer::UpdateUI() {
  641. for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) {
  642. button_map[button]->setText(ButtonToText(buttons_param[button]));
  643. }
  644. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  645. motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id]));
  646. }
  647. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  648. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  649. auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
  650. if (analog_button == nullptr) {
  651. continue;
  652. }
  653. analog_button->setText(
  654. AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
  655. }
  656. analog_map_modifier_button[analog_id]->setText(
  657. ButtonToText(Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")}));
  658. const auto deadzone_label = analog_map_deadzone_label[analog_id];
  659. const auto deadzone_slider = analog_map_deadzone_slider[analog_id];
  660. const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id];
  661. const auto modifier_label = analog_map_modifier_label[analog_id];
  662. const auto modifier_slider = analog_map_modifier_slider[analog_id];
  663. const auto range_groupbox = analog_map_range_groupbox[analog_id];
  664. const auto range_spinbox = analog_map_range_spinbox[analog_id];
  665. int slider_value;
  666. auto& param = analogs_param[analog_id];
  667. const bool is_controller =
  668. param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad";
  669. if (is_controller) {
  670. if (!param.Has("deadzone")) {
  671. param.Set("deadzone", 0.1f);
  672. }
  673. slider_value = static_cast<int>(param.Get("deadzone", 0.1f) * 100);
  674. deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
  675. deadzone_slider->setValue(slider_value);
  676. if (!param.Has("range")) {
  677. param.Set("range", 1.0f);
  678. }
  679. range_spinbox->setValue(static_cast<int>(param.Get("range", 1.0f) * 100));
  680. } else {
  681. if (!param.Has("modifier_scale")) {
  682. param.Set("modifier_scale", 0.5f);
  683. }
  684. slider_value = static_cast<int>(param.Get("modifier_scale", 0.5f) * 100);
  685. modifier_label->setText(tr("Modifier Range: %1%").arg(slider_value));
  686. modifier_slider->setValue(slider_value);
  687. }
  688. deadzone_label->setVisible(is_controller);
  689. deadzone_slider->setVisible(is_controller);
  690. modifier_groupbox->setVisible(!is_controller);
  691. modifier_label->setVisible(!is_controller);
  692. modifier_slider->setVisible(!is_controller);
  693. range_groupbox->setVisible(is_controller);
  694. }
  695. }
  696. void ConfigureInputPlayer::UpdateInputDevices() {
  697. input_devices = input_subsystem->GetInputDevices();
  698. ui->comboDevices->clear();
  699. for (auto device : input_devices) {
  700. ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
  701. }
  702. }
  703. void ConfigureInputPlayer::UpdateControllerIcon() {
  704. // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its
  705. // "nonstandard" to use an image through the icon support)
  706. const QString stylesheet = [this] {
  707. switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
  708. case Settings::ControllerType::ProController:
  709. return QStringLiteral("image: url(:/controller/pro_controller%0)");
  710. case Settings::ControllerType::DualJoyconDetached:
  711. return QStringLiteral("image: url(:/controller/dual_joycon%0)");
  712. case Settings::ControllerType::LeftJoycon:
  713. return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)");
  714. case Settings::ControllerType::RightJoycon:
  715. return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)");
  716. case Settings::ControllerType::Handheld:
  717. return QStringLiteral("image: url(:/controller/handheld%0)");
  718. default:
  719. return QString{};
  720. }
  721. }();
  722. const QString theme = [this] {
  723. if (QIcon::themeName().contains(QStringLiteral("dark"))) {
  724. return QStringLiteral("_dark");
  725. } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
  726. return QStringLiteral("_midnight");
  727. } else {
  728. return QString{};
  729. }
  730. }();
  731. ui->controllerFrame->setStyleSheet(stylesheet.arg(theme));
  732. }
  733. void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
  734. auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
  735. if (debug) {
  736. layout = Settings::ControllerType::ProController;
  737. }
  738. // List of all the widgets that will be hidden by any of the following layouts that need
  739. // "unhidden" after the controller type changes
  740. const std::array<QWidget*, 9> layout_show = {
  741. ui->buttonShoulderButtonsSLSR,
  742. ui->horizontalSpacerShoulderButtonsWidget,
  743. ui->horizontalSpacerShoulderButtonsWidget2,
  744. ui->buttonShoulderButtonsLeft,
  745. ui->buttonMiscButtonsMinusScreenshot,
  746. ui->bottomLeft,
  747. ui->buttonShoulderButtonsRight,
  748. ui->buttonMiscButtonsPlusHome,
  749. ui->bottomRight,
  750. };
  751. for (auto* widget : layout_show) {
  752. widget->show();
  753. }
  754. std::vector<QWidget*> layout_hidden;
  755. switch (layout) {
  756. case Settings::ControllerType::ProController:
  757. case Settings::ControllerType::DualJoyconDetached:
  758. case Settings::ControllerType::Handheld:
  759. layout_hidden = {
  760. ui->buttonShoulderButtonsSLSR,
  761. ui->horizontalSpacerShoulderButtonsWidget2,
  762. };
  763. break;
  764. case Settings::ControllerType::LeftJoycon:
  765. layout_hidden = {
  766. ui->horizontalSpacerShoulderButtonsWidget2,
  767. ui->buttonShoulderButtonsRight,
  768. ui->buttonMiscButtonsPlusHome,
  769. ui->bottomRight,
  770. };
  771. break;
  772. case Settings::ControllerType::RightJoycon:
  773. layout_hidden = {
  774. ui->horizontalSpacerShoulderButtonsWidget,
  775. ui->buttonShoulderButtonsLeft,
  776. ui->buttonMiscButtonsMinusScreenshot,
  777. ui->bottomLeft,
  778. };
  779. break;
  780. }
  781. for (auto* widget : layout_hidden) {
  782. widget->hide();
  783. }
  784. }
  785. void ConfigureInputPlayer::UpdateMotionButtons() {
  786. if (debug) {
  787. // Motion isn't used with the debug controller, hide both groupboxes.
  788. ui->buttonMotionLeftGroup->hide();
  789. ui->buttonMotionRightGroup->hide();
  790. return;
  791. }
  792. // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
  793. switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
  794. case Settings::ControllerType::ProController:
  795. case Settings::ControllerType::LeftJoycon:
  796. case Settings::ControllerType::Handheld:
  797. // Show "Motion 1" and hide "Motion 2".
  798. ui->buttonMotionLeftGroup->show();
  799. ui->buttonMotionRightGroup->hide();
  800. break;
  801. case Settings::ControllerType::RightJoycon:
  802. // Show "Motion 2" and hide "Motion 1".
  803. ui->buttonMotionLeftGroup->hide();
  804. ui->buttonMotionRightGroup->show();
  805. break;
  806. case Settings::ControllerType::DualJoyconDetached:
  807. default:
  808. // Show both "Motion 1/2".
  809. ui->buttonMotionLeftGroup->show();
  810. ui->buttonMotionRightGroup->show();
  811. break;
  812. }
  813. }
  814. void ConfigureInputPlayer::UpdateMappingWithDefaults() {
  815. if (ui->comboDevices->currentIndex() < 2) {
  816. return;
  817. }
  818. const auto& device = input_devices[ui->comboDevices->currentIndex()];
  819. auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
  820. auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
  821. for (std::size_t i = 0; i < buttons_param.size(); ++i) {
  822. buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
  823. }
  824. for (std::size_t i = 0; i < analogs_param.size(); ++i) {
  825. analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
  826. }
  827. UpdateUI();
  828. }
  829. void ConfigureInputPlayer::HandleClick(
  830. QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
  831. InputCommon::Polling::DeviceType type) {
  832. if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
  833. button->setText(tr("Shake!"));
  834. } else {
  835. button->setText(tr("[waiting]"));
  836. }
  837. button->setFocus();
  838. // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
  839. // controller, then they don't want keyboard/mouse input
  840. want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;
  841. input_setter = new_input_setter;
  842. device_pollers = input_subsystem->GetPollers(type);
  843. for (auto& poller : device_pollers) {
  844. poller->Start();
  845. }
  846. QWidget::grabMouse();
  847. QWidget::grabKeyboard();
  848. if (type == InputCommon::Polling::DeviceType::Button) {
  849. input_subsystem->GetGCButtons()->BeginConfiguration();
  850. } else {
  851. input_subsystem->GetGCAnalogs()->BeginConfiguration();
  852. }
  853. if (type == InputCommon::Polling::DeviceType::Motion) {
  854. input_subsystem->GetUDPMotions()->BeginConfiguration();
  855. }
  856. timeout_timer->start(2500); // Cancel after 2.5 seconds
  857. poll_timer->start(50); // Check for new inputs every 50ms
  858. }
  859. void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
  860. timeout_timer->stop();
  861. poll_timer->stop();
  862. for (auto& poller : device_pollers) {
  863. poller->Stop();
  864. }
  865. QWidget::releaseMouse();
  866. QWidget::releaseKeyboard();
  867. input_subsystem->GetGCButtons()->EndConfiguration();
  868. input_subsystem->GetGCAnalogs()->EndConfiguration();
  869. input_subsystem->GetUDPMotions()->EndConfiguration();
  870. if (!abort) {
  871. (*input_setter)(params);
  872. }
  873. UpdateUI();
  874. UpdateInputDeviceCombobox();
  875. input_setter = std::nullopt;
  876. }
  877. bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const {
  878. if (ui->comboDevices->currentIndex() == 0) {
  879. return true;
  880. }
  881. const auto current_input_device = input_devices[ui->comboDevices->currentIndex()];
  882. return params.Get("engine", "") == current_input_device.Get("class", "") &&
  883. params.Get("guid", "") == current_input_device.Get("guid", "") &&
  884. params.Get("port", "") == current_input_device.Get("port", "");
  885. }
  886. void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
  887. if (!input_setter || !event) {
  888. return;
  889. }
  890. if (want_keyboard_mouse) {
  891. SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
  892. false);
  893. } else {
  894. // We don't want any mouse buttons, so don't stop polling
  895. return;
  896. }
  897. SetPollingResult({}, true);
  898. }
  899. void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
  900. if (!input_setter || !event) {
  901. return;
  902. }
  903. if (event->key() != Qt::Key_Escape) {
  904. if (want_keyboard_mouse) {
  905. SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
  906. false);
  907. } else {
  908. // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
  909. return;
  910. }
  911. }
  912. SetPollingResult({}, true);
  913. }
  914. void ConfigureInputPlayer::CreateProfile() {
  915. const auto profile_name =
  916. LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20);
  917. if (profile_name.isEmpty()) {
  918. return;
  919. }
  920. if (!profiles->IsProfileNameValid(profile_name.toStdString())) {
  921. QMessageBox::critical(this, tr("Create Input Profile"),
  922. tr("The given profile name is not valid!"));
  923. return;
  924. }
  925. ApplyConfiguration();
  926. if (!profiles->CreateProfile(profile_name.toStdString(), player_index)) {
  927. QMessageBox::critical(this, tr("Create Input Profile"),
  928. tr("Failed to create the input profile \"%1\"").arg(profile_name));
  929. RefreshInputProfiles();
  930. return;
  931. }
  932. ui->comboProfiles->addItem(profile_name);
  933. ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1);
  934. }
  935. void ConfigureInputPlayer::DeleteProfile() {
  936. const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
  937. if (profile_name.isEmpty()) {
  938. return;
  939. }
  940. if (!profiles->DeleteProfile(profile_name.toStdString())) {
  941. QMessageBox::critical(this, tr("Delete Input Profile"),
  942. tr("Failed to delete the input profile \"%1\"").arg(profile_name));
  943. RefreshInputProfiles();
  944. return;
  945. }
  946. ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex());
  947. ui->comboProfiles->setCurrentIndex(-1);
  948. }
  949. void ConfigureInputPlayer::LoadProfile() {
  950. const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
  951. if (profile_name.isEmpty()) {
  952. return;
  953. }
  954. ApplyConfiguration();
  955. if (!profiles->LoadProfile(profile_name.toStdString(), player_index)) {
  956. QMessageBox::critical(this, tr("Load Input Profile"),
  957. tr("Failed to load the input profile \"%1\"").arg(profile_name));
  958. RefreshInputProfiles();
  959. return;
  960. }
  961. LoadConfiguration();
  962. }
  963. void ConfigureInputPlayer::SaveProfile() {
  964. const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
  965. if (profile_name.isEmpty()) {
  966. return;
  967. }
  968. ApplyConfiguration();
  969. if (!profiles->SaveProfile(profile_name.toStdString(), player_index)) {
  970. QMessageBox::critical(this, tr("Save Input Profile"),
  971. tr("Failed to save the input profile \"%1\"").arg(profile_name));
  972. RefreshInputProfiles();
  973. return;
  974. }
  975. }
  976. void ConfigureInputPlayer::RefreshInputProfiles() {
  977. ui->comboProfiles->clear();
  978. for (const auto& profile_name : profiles->GetInputProfileNames()) {
  979. ui->comboProfiles->addItem(QString::fromStdString(profile_name));
  980. }
  981. ui->comboProfiles->setCurrentIndex(-1);
  982. }