qt_controller.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <algorithm>
  4. #include <thread>
  5. #include "common/assert.h"
  6. #include "common/settings.h"
  7. #include "common/settings_enums.h"
  8. #include "common/string_util.h"
  9. #include "core/core.h"
  10. #include "core/hle/service/sm/sm.h"
  11. #include "hid_core/frontend/emulated_controller.h"
  12. #include "hid_core/hid_core.h"
  13. #include "hid_core/hid_types.h"
  14. #include "hid_core/resources/npad/npad.h"
  15. #include "ui_qt_controller.h"
  16. #include "yuzu/applets/qt_controller.h"
  17. #include "yuzu/configuration/configure_input.h"
  18. #include "yuzu/configuration/configure_input_profile_dialog.h"
  19. #include "yuzu/configuration/configure_motion_touch.h"
  20. #include "yuzu/configuration/configure_vibration.h"
  21. #include "yuzu/configuration/input_profiles.h"
  22. #include "yuzu/main.h"
  23. #include "yuzu/util/controller_navigation.h"
  24. namespace {
  25. void UpdateController(Core::HID::EmulatedController* controller,
  26. Core::HID::NpadStyleIndex controller_type, bool connected) {
  27. if (controller->IsConnected(true)) {
  28. controller->Disconnect();
  29. }
  30. controller->SetNpadStyleIndex(controller_type);
  31. if (connected) {
  32. controller->Connect(true);
  33. }
  34. }
  35. // Returns true if the given controller type is compatible with the given parameters.
  36. bool IsControllerCompatible(Core::HID::NpadStyleIndex controller_type,
  37. Core::Frontend::ControllerParameters parameters) {
  38. switch (controller_type) {
  39. case Core::HID::NpadStyleIndex::Fullkey:
  40. return parameters.allow_pro_controller;
  41. case Core::HID::NpadStyleIndex::JoyconDual:
  42. return parameters.allow_dual_joycons;
  43. case Core::HID::NpadStyleIndex::JoyconLeft:
  44. return parameters.allow_left_joycon;
  45. case Core::HID::NpadStyleIndex::JoyconRight:
  46. return parameters.allow_right_joycon;
  47. case Core::HID::NpadStyleIndex::Handheld:
  48. return parameters.enable_single_mode && parameters.allow_handheld;
  49. case Core::HID::NpadStyleIndex::GameCube:
  50. return parameters.allow_gamecube_controller;
  51. default:
  52. return false;
  53. }
  54. }
  55. } // namespace
  56. QtControllerSelectorDialog::QtControllerSelectorDialog(
  57. QWidget* parent, Core::Frontend::ControllerParameters parameters_,
  58. InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
  59. : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
  60. parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
  61. input_profiles(std::make_unique<InputProfiles>()), system{system_} {
  62. ui->setupUi(this);
  63. player_widgets = {
  64. ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4,
  65. ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8,
  66. };
  67. player_groupboxes = {
  68. ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected,
  69. ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected,
  70. ui->groupPlayer7Connected, ui->groupPlayer8Connected,
  71. };
  72. connected_controller_icons = {
  73. ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4,
  74. ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8,
  75. };
  76. led_patterns_boxes = {{
  77. {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3,
  78. ui->checkboxPlayer1LED4},
  79. {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3,
  80. ui->checkboxPlayer2LED4},
  81. {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3,
  82. ui->checkboxPlayer3LED4},
  83. {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3,
  84. ui->checkboxPlayer4LED4},
  85. {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3,
  86. ui->checkboxPlayer5LED4},
  87. {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3,
  88. ui->checkboxPlayer6LED4},
  89. {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3,
  90. ui->checkboxPlayer7LED4},
  91. {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3,
  92. ui->checkboxPlayer8LED4},
  93. }};
  94. explain_text_labels = {
  95. ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain,
  96. ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain,
  97. ui->labelPlayer7Explain, ui->labelPlayer8Explain,
  98. };
  99. emulated_controllers = {
  100. ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated,
  101. ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated,
  102. ui->comboPlayer7Emulated, ui->comboPlayer8Emulated,
  103. };
  104. player_labels = {
  105. ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4,
  106. ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8,
  107. };
  108. connected_controller_labels = {
  109. ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3,
  110. ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6,
  111. ui->labelConnectedPlayer7, ui->labelConnectedPlayer8,
  112. };
  113. connected_controller_checkboxes = {
  114. ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
  115. ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
  116. ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
  117. };
  118. ui->labelError->setVisible(false);
  119. // Setup/load everything prior to setting up connections.
  120. // This avoids unintentionally changing the states of elements while loading them in.
  121. SetSupportedControllers();
  122. DisableUnsupportedPlayers();
  123. for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) {
  124. SetEmulatedControllers(player_index);
  125. }
  126. LoadConfiguration();
  127. controller_navigation = new ControllerNavigation(system.HIDCore(), this);
  128. for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
  129. SetExplainText(i);
  130. UpdateControllerIcon(i);
  131. UpdateLEDPattern(i);
  132. UpdateBorderColor(i);
  133. connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
  134. // Reconnect current controller if it was the last one checked
  135. // (player number was reduced by more than one)
  136. const bool reconnect_first = !checked && i < player_groupboxes.size() - 1 &&
  137. player_groupboxes[i + 1]->isChecked();
  138. // Ensures that connecting a controller changes the number of players
  139. if (connected_controller_checkboxes[i]->isChecked() != checked) {
  140. // Ensures that the players are always connected in sequential order
  141. PropagatePlayerNumberChanged(i, checked, reconnect_first);
  142. }
  143. });
  144. connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) {
  145. // Reconnect current controller if it was the last one checked
  146. // (player number was reduced by more than one)
  147. const bool reconnect_first = !checked &&
  148. i < connected_controller_checkboxes.size() - 1 &&
  149. connected_controller_checkboxes[i + 1]->isChecked();
  150. // Ensures that the players are always connected in sequential order
  151. PropagatePlayerNumberChanged(i, checked, reconnect_first);
  152. });
  153. connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
  154. [this, i](int) {
  155. UpdateControllerIcon(i);
  156. UpdateControllerState(i);
  157. UpdateLEDPattern(i);
  158. CheckIfParametersMet();
  159. });
  160. connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
  161. player_groupboxes[i]->setChecked(state == Qt::Checked);
  162. UpdateControllerIcon(i);
  163. UpdateControllerState(i);
  164. UpdateLEDPattern(i);
  165. UpdateBorderColor(i);
  166. CheckIfParametersMet();
  167. });
  168. if (i == 0) {
  169. connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
  170. [this, i](int index) {
  171. UpdateDockedState(GetControllerTypeFromIndex(index, i) ==
  172. Core::HID::NpadStyleIndex::Handheld);
  173. });
  174. }
  175. }
  176. connect(ui->vibrationButton, &QPushButton::clicked, this,
  177. &QtControllerSelectorDialog::CallConfigureVibrationDialog);
  178. connect(ui->motionButton, &QPushButton::clicked, this,
  179. &QtControllerSelectorDialog::CallConfigureMotionTouchDialog);
  180. connect(ui->inputConfigButton, &QPushButton::clicked, this,
  181. &QtControllerSelectorDialog::CallConfigureInputProfileDialog);
  182. connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
  183. &QtControllerSelectorDialog::ApplyConfiguration);
  184. connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
  185. [this](Qt::Key key) {
  186. QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
  187. QCoreApplication::postEvent(this, event);
  188. });
  189. // Enhancement: Check if the parameters have already been met before disconnecting controllers.
  190. // If all the parameters are met AND only allows a single player,
  191. // stop the constructor here as we do not need to continue.
  192. if (CheckIfParametersMet() && parameters.enable_single_mode) {
  193. return;
  194. }
  195. // If keep_controllers_connected is false, forcefully disconnect all controllers
  196. if (!parameters.keep_controllers_connected) {
  197. for (auto player : player_groupboxes) {
  198. player->setChecked(false);
  199. }
  200. }
  201. resize(0, 0);
  202. }
  203. QtControllerSelectorDialog::~QtControllerSelectorDialog() {
  204. controller_navigation->UnloadController();
  205. system.HIDCore().DisableAllControllerConfiguration();
  206. }
  207. int QtControllerSelectorDialog::exec() {
  208. if (parameters_met && parameters.enable_single_mode) {
  209. return QDialog::Accepted;
  210. }
  211. return QDialog::exec();
  212. }
  213. void QtControllerSelectorDialog::ApplyConfiguration() {
  214. const bool pre_docked_mode = Settings::IsDockedMode();
  215. const bool docked_mode_selected = ui->radioDocked->isChecked();
  216. Settings::values.use_docked_mode.SetValue(
  217. docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
  218. OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
  219. Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
  220. Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
  221. }
  222. void QtControllerSelectorDialog::LoadConfiguration() {
  223. system.HIDCore().EnableAllControllerConfiguration();
  224. const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
  225. for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
  226. const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(index);
  227. const auto connected =
  228. controller->IsConnected(true) || (index == 0 && handheld->IsConnected(true));
  229. player_groupboxes[index]->setChecked(connected);
  230. connected_controller_checkboxes[index]->setChecked(connected);
  231. emulated_controllers[index]->setCurrentIndex(
  232. GetIndexFromControllerType(controller->GetNpadStyleIndex(true), index));
  233. }
  234. UpdateDockedState(handheld->IsConnected(true));
  235. ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
  236. ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
  237. }
  238. void QtControllerSelectorDialog::CallConfigureVibrationDialog() {
  239. ConfigureVibration dialog(this, system.HIDCore());
  240. dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
  241. Qt::WindowSystemMenuHint);
  242. dialog.setWindowModality(Qt::WindowModal);
  243. if (dialog.exec() == QDialog::Accepted) {
  244. dialog.ApplyConfiguration();
  245. }
  246. }
  247. void QtControllerSelectorDialog::CallConfigureMotionTouchDialog() {
  248. ConfigureMotionTouch dialog(this, input_subsystem);
  249. dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
  250. Qt::WindowSystemMenuHint);
  251. dialog.setWindowModality(Qt::WindowModal);
  252. if (dialog.exec() == QDialog::Accepted) {
  253. dialog.ApplyConfiguration();
  254. }
  255. }
  256. void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
  257. ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get(), system);
  258. dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
  259. Qt::WindowSystemMenuHint);
  260. dialog.setWindowModality(Qt::WindowModal);
  261. dialog.exec();
  262. }
  263. void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
  264. const auto num_connected_players = static_cast<int>(
  265. std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
  266. [](const QGroupBox* player) { return player->isChecked(); }));
  267. const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
  268. const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
  269. if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
  270. // Display error message when trying to validate using "Enter" and "OK" button is disabled
  271. ui->labelError->setVisible(true);
  272. return;
  273. } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
  274. // Remove a player if possible
  275. connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
  276. return;
  277. } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
  278. // Add a player, if possible
  279. ui->labelError->setVisible(false);
  280. connected_controller_checkboxes[num_connected_players]->setChecked(true);
  281. return;
  282. }
  283. QDialog::keyPressEvent(evt);
  284. }
  285. bool QtControllerSelectorDialog::CheckIfParametersMet() {
  286. // Here, we check and validate the current configuration against all applicable parameters.
  287. const auto num_connected_players = static_cast<int>(
  288. std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
  289. [](const QGroupBox* player) { return player->isChecked(); }));
  290. const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
  291. const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
  292. // First, check against the number of connected players.
  293. if (num_connected_players < min_supported_players ||
  294. num_connected_players > max_supported_players) {
  295. parameters_met = false;
  296. ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(parameters_met);
  297. return parameters_met;
  298. }
  299. // Next, check against all connected controllers.
  300. const auto all_controllers_compatible = [this] {
  301. for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
  302. // Skip controllers that are not used, we only care about the currently connected ones.
  303. if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) {
  304. continue;
  305. }
  306. const auto compatible = IsControllerCompatible(
  307. GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex(), index),
  308. parameters);
  309. // If any controller is found to be incompatible, return false early.
  310. if (!compatible) {
  311. return false;
  312. }
  313. }
  314. // Reaching here means all currently connected controllers are compatible.
  315. return true;
  316. }();
  317. parameters_met = all_controllers_compatible;
  318. ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(parameters_met);
  319. return parameters_met;
  320. }
  321. void QtControllerSelectorDialog::SetSupportedControllers() {
  322. const QString theme = [] {
  323. if (QIcon::themeName().contains(QStringLiteral("dark"))) {
  324. return QStringLiteral("_dark");
  325. } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
  326. return QStringLiteral("_midnight");
  327. } else {
  328. return QString{};
  329. }
  330. }();
  331. if (parameters.enable_single_mode && parameters.allow_handheld) {
  332. ui->controllerSupported1->setStyleSheet(
  333. QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme));
  334. } else {
  335. ui->controllerSupported1->setStyleSheet(
  336. QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme));
  337. }
  338. if (parameters.allow_dual_joycons) {
  339. ui->controllerSupported2->setStyleSheet(
  340. QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme));
  341. } else {
  342. ui->controllerSupported2->setStyleSheet(
  343. QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme));
  344. }
  345. if (parameters.allow_left_joycon) {
  346. ui->controllerSupported3->setStyleSheet(
  347. QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme));
  348. } else {
  349. ui->controllerSupported3->setStyleSheet(
  350. QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme));
  351. }
  352. if (parameters.allow_right_joycon) {
  353. ui->controllerSupported4->setStyleSheet(
  354. QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme));
  355. } else {
  356. ui->controllerSupported4->setStyleSheet(
  357. QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
  358. }
  359. if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) {
  360. ui->controllerSupported5->setStyleSheet(
  361. QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
  362. } else {
  363. ui->controllerSupported5->setStyleSheet(
  364. QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ")
  365. .arg(theme));
  366. }
  367. // enable_single_mode overrides min_players and max_players.
  368. if (parameters.enable_single_mode) {
  369. ui->numberSupportedLabel->setText(QStringLiteral("1"));
  370. return;
  371. }
  372. if (parameters.min_players == parameters.max_players) {
  373. ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players));
  374. } else {
  375. ui->numberSupportedLabel->setText(
  376. QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players));
  377. }
  378. }
  379. void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index) {
  380. const auto npad_style_set = system.HIDCore().GetSupportedStyleTag();
  381. auto& pairs = index_controller_type_pairs[player_index];
  382. pairs.clear();
  383. emulated_controllers[player_index]->clear();
  384. const auto add_item = [&](Core::HID::NpadStyleIndex controller_type,
  385. const QString& controller_name) {
  386. pairs.emplace_back(emulated_controllers[player_index]->count(), controller_type);
  387. emulated_controllers[player_index]->addItem(controller_name);
  388. };
  389. if (npad_style_set.fullkey == 1) {
  390. add_item(Core::HID::NpadStyleIndex::Fullkey, tr("Pro Controller"));
  391. }
  392. if (npad_style_set.joycon_dual == 1) {
  393. add_item(Core::HID::NpadStyleIndex::JoyconDual, tr("Dual Joycons"));
  394. }
  395. if (npad_style_set.joycon_left == 1) {
  396. add_item(Core::HID::NpadStyleIndex::JoyconLeft, tr("Left Joycon"));
  397. }
  398. if (npad_style_set.joycon_right == 1) {
  399. add_item(Core::HID::NpadStyleIndex::JoyconRight, tr("Right Joycon"));
  400. }
  401. if (player_index == 0 && npad_style_set.handheld == 1) {
  402. add_item(Core::HID::NpadStyleIndex::Handheld, tr("Handheld"));
  403. }
  404. if (npad_style_set.gamecube == 1) {
  405. add_item(Core::HID::NpadStyleIndex::GameCube, tr("GameCube Controller"));
  406. }
  407. // Disable all unsupported controllers
  408. if (!Settings::values.enable_all_controllers) {
  409. return;
  410. }
  411. if (npad_style_set.palma == 1) {
  412. add_item(Core::HID::NpadStyleIndex::Pokeball, tr("Poke Ball Plus"));
  413. }
  414. if (npad_style_set.lark == 1) {
  415. add_item(Core::HID::NpadStyleIndex::NES, tr("NES Controller"));
  416. }
  417. if (npad_style_set.lucia == 1) {
  418. add_item(Core::HID::NpadStyleIndex::SNES, tr("SNES Controller"));
  419. }
  420. if (npad_style_set.lagoon == 1) {
  421. add_item(Core::HID::NpadStyleIndex::N64, tr("N64 Controller"));
  422. }
  423. if (npad_style_set.lager == 1) {
  424. add_item(Core::HID::NpadStyleIndex::SegaGenesis, tr("Sega Genesis"));
  425. }
  426. }
  427. Core::HID::NpadStyleIndex QtControllerSelectorDialog::GetControllerTypeFromIndex(
  428. int index, std::size_t player_index) const {
  429. const auto& pairs = index_controller_type_pairs[player_index];
  430. const auto it = std::find_if(pairs.begin(), pairs.end(),
  431. [index](const auto& pair) { return pair.first == index; });
  432. if (it == pairs.end()) {
  433. return Core::HID::NpadStyleIndex::Fullkey;
  434. }
  435. return it->second;
  436. }
  437. int QtControllerSelectorDialog::GetIndexFromControllerType(Core::HID::NpadStyleIndex type,
  438. std::size_t player_index) const {
  439. const auto& pairs = index_controller_type_pairs[player_index];
  440. const auto it = std::find_if(pairs.begin(), pairs.end(),
  441. [type](const auto& pair) { return pair.second == type; });
  442. if (it == pairs.end()) {
  443. return 0;
  444. }
  445. return it->first;
  446. }
  447. void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) {
  448. if (!player_groupboxes[player_index]->isChecked()) {
  449. connected_controller_icons[player_index]->setStyleSheet(QString{});
  450. player_labels[player_index]->show();
  451. return;
  452. }
  453. const QString stylesheet = [this, player_index] {
  454. switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
  455. player_index)) {
  456. case Core::HID::NpadStyleIndex::Fullkey:
  457. case Core::HID::NpadStyleIndex::GameCube:
  458. return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
  459. case Core::HID::NpadStyleIndex::JoyconDual:
  460. return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
  461. case Core::HID::NpadStyleIndex::JoyconLeft:
  462. return QStringLiteral("image: url(:/controller/applet_joycon_left%0); ");
  463. case Core::HID::NpadStyleIndex::JoyconRight:
  464. return QStringLiteral("image: url(:/controller/applet_joycon_right%0); ");
  465. case Core::HID::NpadStyleIndex::Handheld:
  466. return QStringLiteral("image: url(:/controller/applet_handheld%0); ");
  467. default:
  468. return QString{};
  469. }
  470. }();
  471. if (stylesheet.isEmpty()) {
  472. connected_controller_icons[player_index]->setStyleSheet(QString{});
  473. player_labels[player_index]->show();
  474. return;
  475. }
  476. const QString theme = [] {
  477. if (QIcon::themeName().contains(QStringLiteral("dark"))) {
  478. return QStringLiteral("_dark");
  479. } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
  480. return QStringLiteral("_midnight");
  481. } else {
  482. return QString{};
  483. }
  484. }();
  485. connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme));
  486. player_labels[player_index]->hide();
  487. }
  488. void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
  489. auto* controller = system.HIDCore().GetEmulatedControllerByIndex(player_index);
  490. const auto controller_type = GetControllerTypeFromIndex(
  491. emulated_controllers[player_index]->currentIndex(), player_index);
  492. const auto player_connected = player_groupboxes[player_index]->isChecked() &&
  493. controller_type != Core::HID::NpadStyleIndex::Handheld;
  494. if (controller->GetNpadStyleIndex(true) == controller_type &&
  495. controller->IsConnected(true) == player_connected) {
  496. return;
  497. }
  498. // Disconnect the controller first.
  499. UpdateController(controller, controller_type, false);
  500. // Handheld
  501. if (player_index == 0) {
  502. if (controller_type == Core::HID::NpadStyleIndex::Handheld) {
  503. auto* handheld =
  504. system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
  505. UpdateController(handheld, Core::HID::NpadStyleIndex::Handheld,
  506. player_groupboxes[player_index]->isChecked());
  507. }
  508. }
  509. UpdateController(controller, controller_type, player_connected);
  510. }
  511. void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
  512. if (!player_groupboxes[player_index]->isChecked() ||
  513. GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
  514. player_index) == Core::HID::NpadStyleIndex::Handheld) {
  515. led_patterns_boxes[player_index][0]->setChecked(false);
  516. led_patterns_boxes[player_index][1]->setChecked(false);
  517. led_patterns_boxes[player_index][2]->setChecked(false);
  518. led_patterns_boxes[player_index][3]->setChecked(false);
  519. return;
  520. }
  521. const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(player_index);
  522. const auto led_pattern = controller->GetLedPattern();
  523. led_patterns_boxes[player_index][0]->setChecked(led_pattern.position1);
  524. led_patterns_boxes[player_index][1]->setChecked(led_pattern.position2);
  525. led_patterns_boxes[player_index][2]->setChecked(led_pattern.position3);
  526. led_patterns_boxes[player_index][3]->setChecked(led_pattern.position4);
  527. }
  528. void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) {
  529. if (!parameters.enable_border_color ||
  530. player_index >= static_cast<std::size_t>(parameters.max_players) ||
  531. player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) {
  532. return;
  533. }
  534. player_groupboxes[player_index]->setStyleSheet(
  535. player_groupboxes[player_index]->styleSheet().append(
  536. QStringLiteral("QGroupBox#groupPlayer%1Connected:checked "
  537. "{ border: 1px solid rgba(%2, %3, %4, %5); }")
  538. .arg(player_index + 1)
  539. .arg(parameters.border_colors[player_index][0])
  540. .arg(parameters.border_colors[player_index][1])
  541. .arg(parameters.border_colors[player_index][2])
  542. .arg(parameters.border_colors[player_index][3])));
  543. }
  544. void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) {
  545. if (!parameters.enable_explain_text ||
  546. player_index >= static_cast<std::size_t>(parameters.max_players)) {
  547. return;
  548. }
  549. explain_text_labels[player_index]->setText(QString::fromStdString(
  550. Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(),
  551. parameters.explain_text[player_index].size())));
  552. }
  553. void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
  554. // Disallow changing the console mode if the controller type is handheld.
  555. ui->radioDocked->setEnabled(!is_handheld);
  556. ui->radioUndocked->setEnabled(!is_handheld);
  557. ui->radioDocked->setChecked(Settings::IsDockedMode());
  558. ui->radioUndocked->setChecked(!Settings::IsDockedMode());
  559. // Also force into undocked mode if the controller type is handheld.
  560. if (is_handheld) {
  561. ui->radioUndocked->setChecked(true);
  562. }
  563. }
  564. void QtControllerSelectorDialog::PropagatePlayerNumberChanged(size_t player_index, bool checked,
  565. bool reconnect_current) {
  566. connected_controller_checkboxes[player_index]->setChecked(checked);
  567. // Hide eventual error message about number of controllers
  568. ui->labelError->setVisible(false);
  569. if (checked) {
  570. // Check all previous buttons when checked
  571. if (player_index > 0) {
  572. PropagatePlayerNumberChanged(player_index - 1, checked);
  573. }
  574. } else {
  575. // Unchecked all following buttons when unchecked
  576. if (player_index < connected_controller_checkboxes.size() - 1) {
  577. PropagatePlayerNumberChanged(player_index + 1, checked);
  578. }
  579. }
  580. if (reconnect_current) {
  581. connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked);
  582. }
  583. }
  584. void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
  585. const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
  586. switch (max_supported_players) {
  587. case 0:
  588. default:
  589. ASSERT(false);
  590. return;
  591. case 1:
  592. ui->widgetSpacer->hide();
  593. ui->widgetSpacer2->hide();
  594. ui->widgetSpacer3->hide();
  595. ui->widgetSpacer4->hide();
  596. break;
  597. case 2:
  598. ui->widgetSpacer->hide();
  599. ui->widgetSpacer2->hide();
  600. ui->widgetSpacer3->hide();
  601. break;
  602. case 3:
  603. ui->widgetSpacer->hide();
  604. ui->widgetSpacer2->hide();
  605. break;
  606. case 4:
  607. ui->widgetSpacer->hide();
  608. break;
  609. case 5:
  610. case 6:
  611. case 7:
  612. case 8:
  613. break;
  614. }
  615. for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) {
  616. auto* controller = system.HIDCore().GetEmulatedControllerByIndex(index);
  617. // Disconnect any unsupported players here and disable or hide them if applicable.
  618. UpdateController(controller, controller->GetNpadStyleIndex(true), false);
  619. // Hide the player widgets when max_supported_controllers is less than or equal to 4.
  620. if (max_supported_players <= 4) {
  621. player_widgets[index]->hide();
  622. }
  623. // Disable and hide the following to prevent these from interaction.
  624. player_widgets[index]->setDisabled(true);
  625. connected_controller_checkboxes[index]->setDisabled(true);
  626. connected_controller_labels[index]->hide();
  627. connected_controller_checkboxes[index]->hide();
  628. }
  629. }
  630. QtControllerSelector::QtControllerSelector(GMainWindow& parent) {
  631. connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent,
  632. &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection);
  633. connect(this, &QtControllerSelector::MainWindowRequestExit, &parent,
  634. &GMainWindow::ControllerSelectorRequestExit, Qt::QueuedConnection);
  635. connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this,
  636. &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection);
  637. }
  638. QtControllerSelector::~QtControllerSelector() = default;
  639. void QtControllerSelector::Close() const {
  640. callback = {};
  641. emit MainWindowRequestExit();
  642. }
  643. void QtControllerSelector::ReconfigureControllers(
  644. ReconfigureCallback callback_, const Core::Frontend::ControllerParameters& parameters) const {
  645. callback = std::move(callback_);
  646. emit MainWindowReconfigureControllers(parameters);
  647. }
  648. void QtControllerSelector::MainWindowReconfigureFinished(bool is_success) {
  649. if (callback) {
  650. callback(is_success);
  651. }
  652. }