qt_controller.cpp 27 KB

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