configure_input.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. // SPDX-FileCopyrightText: 2016 Citra Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <memory>
  4. #include <thread>
  5. #include "common/settings.h"
  6. #include "common/settings_enums.h"
  7. #include "core/core.h"
  8. #include "core/hle/service/am/am.h"
  9. #include "core/hle/service/am/applet_ae.h"
  10. #include "core/hle/service/am/applet_manager.h"
  11. #include "core/hle/service/am/applet_message_queue.h"
  12. #include "core/hle/service/am/applet_oe.h"
  13. #include "core/hle/service/sm/sm.h"
  14. #include "hid_core/frontend/emulated_controller.h"
  15. #include "hid_core/hid_core.h"
  16. #include "ui_configure_input.h"
  17. #include "ui_configure_input_advanced.h"
  18. #include "ui_configure_input_player.h"
  19. #include "yuzu/configuration/configure_camera.h"
  20. #include "yuzu/configuration/configure_debug_controller.h"
  21. #include "yuzu/configuration/configure_input.h"
  22. #include "yuzu/configuration/configure_input_advanced.h"
  23. #include "yuzu/configuration/configure_input_player.h"
  24. #include "yuzu/configuration/configure_motion_touch.h"
  25. #include "yuzu/configuration/configure_ringcon.h"
  26. #include "yuzu/configuration/configure_touchscreen_advanced.h"
  27. #include "yuzu/configuration/configure_vibration.h"
  28. #include "yuzu/configuration/input_profiles.h"
  29. namespace {
  30. template <typename Dialog, typename... Args>
  31. void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
  32. Dialog dialog(&parent, std::forward<Args>(args)...);
  33. const auto res = dialog.exec();
  34. if (res == QDialog::Accepted) {
  35. dialog.ApplyConfiguration();
  36. }
  37. }
  38. } // Anonymous namespace
  39. void OnDockedModeChanged(bool last_state, bool new_state, Core::System& system) {
  40. if (last_state == new_state) {
  41. return;
  42. }
  43. if (!system.IsPoweredOn()) {
  44. return;
  45. }
  46. system.GetAppletManager().OperationModeChanged();
  47. }
  48. ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent)
  49. : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
  50. profiles(std::make_unique<InputProfiles>()), system{system_} {
  51. ui->setupUi(this);
  52. }
  53. ConfigureInput::~ConfigureInput() = default;
  54. void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
  55. std::size_t max_players) {
  56. const bool is_powered_on = system.IsPoweredOn();
  57. auto& hid_core = system.HIDCore();
  58. player_controllers = {
  59. new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem, profiles.get(),
  60. hid_core, is_powered_on),
  61. new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem, profiles.get(),
  62. hid_core, is_powered_on),
  63. new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem, profiles.get(),
  64. hid_core, is_powered_on),
  65. new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem, profiles.get(),
  66. hid_core, is_powered_on),
  67. new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem, profiles.get(),
  68. hid_core, is_powered_on),
  69. new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem, profiles.get(),
  70. hid_core, is_powered_on),
  71. new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem, profiles.get(),
  72. hid_core, is_powered_on),
  73. new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem, profiles.get(),
  74. hid_core, is_powered_on),
  75. };
  76. player_tabs = {
  77. ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4,
  78. ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8,
  79. };
  80. connected_controller_checkboxes = {
  81. ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
  82. ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
  83. ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
  84. };
  85. std::array<QLabel*, 8> connected_controller_labels = {
  86. ui->label, ui->label_3, ui->label_4, ui->label_5,
  87. ui->label_6, ui->label_7, ui->label_8, ui->label_9,
  88. };
  89. for (std::size_t i = 0; i < player_tabs.size(); ++i) {
  90. player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
  91. player_tabs[i]->layout()->addWidget(player_controllers[i]);
  92. connect(player_controllers[i], &ConfigureInputPlayer::Connected, [this, i](bool checked) {
  93. // Ensures that connecting a controller changes the number of players
  94. if (connected_controller_checkboxes[i]->isChecked() != checked) {
  95. // Ensures that the players are always connected in sequential order
  96. PropagatePlayerNumberChanged(i, checked);
  97. }
  98. });
  99. connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) {
  100. // Reconnect current controller if it was the last one checked
  101. // (player number was reduced by more than one)
  102. const bool reconnect_first = !checked &&
  103. i < connected_controller_checkboxes.size() - 1 &&
  104. connected_controller_checkboxes[i + 1]->isChecked();
  105. // Ensures that the players are always connected in sequential order
  106. PropagatePlayerNumberChanged(i, checked, reconnect_first);
  107. });
  108. connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
  109. &ConfigureInput::UpdateAllInputDevices);
  110. connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this,
  111. &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection);
  112. connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
  113. // Keep activated controllers synced with the "Connected Controllers" checkboxes
  114. player_controllers[i]->ConnectPlayer(state == Qt::Checked);
  115. });
  116. // Remove/hide all the elements that exceed max_players, if applicable.
  117. if (i >= max_players) {
  118. ui->tabWidget->removeTab(static_cast<int>(max_players));
  119. connected_controller_checkboxes[i]->hide();
  120. connected_controller_labels[i]->hide();
  121. }
  122. }
  123. // Only the first player can choose handheld mode so connect the signal just to player 1
  124. connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged,
  125. [this](bool is_handheld) { UpdateDockedState(is_handheld); });
  126. advanced = new ConfigureInputAdvanced(hid_core, this);
  127. ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
  128. ui->tabAdvanced->layout()->addWidget(advanced);
  129. connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog,
  130. [this, input_subsystem, &hid_core, is_powered_on] {
  131. CallConfigureDialog<ConfigureDebugController>(
  132. *this, input_subsystem, profiles.get(), hid_core, is_powered_on);
  133. });
  134. connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog,
  135. [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });
  136. connect(advanced, &ConfigureInputAdvanced::CallMotionTouchConfigDialog,
  137. [this, input_subsystem] {
  138. CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
  139. });
  140. connect(advanced, &ConfigureInputAdvanced::CallRingControllerDialog,
  141. [this, input_subsystem, &hid_core] {
  142. CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core);
  143. });
  144. connect(advanced, &ConfigureInputAdvanced::CallCameraDialog, [this, input_subsystem] {
  145. CallConfigureDialog<ConfigureCamera>(*this, input_subsystem);
  146. });
  147. connect(ui->vibrationButton, &QPushButton::clicked,
  148. [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
  149. connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] {
  150. CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
  151. });
  152. connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
  153. connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
  154. RetranslateUI();
  155. LoadConfiguration();
  156. }
  157. void ConfigureInput::PropagatePlayerNumberChanged(size_t player_index, bool checked,
  158. bool reconnect_current) {
  159. connected_controller_checkboxes[player_index]->setChecked(checked);
  160. if (checked) {
  161. // Check all previous buttons when checked
  162. if (player_index > 0) {
  163. PropagatePlayerNumberChanged(player_index - 1, checked);
  164. }
  165. } else {
  166. // Unchecked all following buttons when unchecked
  167. if (player_index < connected_controller_checkboxes.size() - 1) {
  168. PropagatePlayerNumberChanged(player_index + 1, checked);
  169. }
  170. }
  171. if (reconnect_current) {
  172. connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked);
  173. }
  174. }
  175. QList<QWidget*> ConfigureInput::GetSubTabs() const {
  176. return {
  177. ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
  178. ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8, ui->tabAdvanced,
  179. };
  180. }
  181. void ConfigureInput::ApplyConfiguration() {
  182. const bool was_global = Settings::values.players.UsingGlobal();
  183. Settings::values.players.SetGlobal(true);
  184. for (auto* controller : player_controllers) {
  185. controller->ApplyConfiguration();
  186. }
  187. advanced->ApplyConfiguration();
  188. const bool pre_docked_mode = Settings::IsDockedMode();
  189. const bool docked_mode_selected = ui->radioDocked->isChecked();
  190. Settings::values.use_docked_mode.SetValue(
  191. docked_mode_selected ? Settings::ConsoleMode::Docked : Settings::ConsoleMode::Handheld);
  192. OnDockedModeChanged(pre_docked_mode, docked_mode_selected, system);
  193. Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
  194. Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
  195. Settings::values.players.SetGlobal(was_global);
  196. }
  197. void ConfigureInput::changeEvent(QEvent* event) {
  198. if (event->type() == QEvent::LanguageChange) {
  199. RetranslateUI();
  200. }
  201. QWidget::changeEvent(event);
  202. }
  203. void ConfigureInput::RetranslateUI() {
  204. ui->retranslateUi(this);
  205. }
  206. void ConfigureInput::LoadConfiguration() {
  207. const auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
  208. LoadPlayerControllerIndices();
  209. UpdateDockedState(handheld->IsConnected());
  210. ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
  211. ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
  212. }
  213. void ConfigureInput::LoadPlayerControllerIndices() {
  214. for (std::size_t i = 0; i < connected_controller_checkboxes.size(); ++i) {
  215. if (i == 0) {
  216. auto* handheld =
  217. system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
  218. if (handheld->IsConnected()) {
  219. connected_controller_checkboxes[i]->setChecked(true);
  220. continue;
  221. }
  222. }
  223. const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(i);
  224. connected_controller_checkboxes[i]->setChecked(controller->IsConnected());
  225. }
  226. }
  227. void ConfigureInput::ClearAll() {
  228. // We don't have a good way to know what tab is active, but we can find out by getting the
  229. // parent of the consoleInputSettings
  230. auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent());
  231. player_tab->ClearAll();
  232. }
  233. void ConfigureInput::RestoreDefaults() {
  234. // We don't have a good way to know what tab is active, but we can find out by getting the
  235. // parent of the consoleInputSettings
  236. auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent());
  237. player_tab->RestoreDefaults();
  238. ui->radioDocked->setChecked(true);
  239. ui->radioUndocked->setChecked(false);
  240. ui->vibrationGroup->setChecked(true);
  241. ui->motionGroup->setChecked(true);
  242. }
  243. void ConfigureInput::UpdateDockedState(bool is_handheld) {
  244. // Disallow changing the console mode if the controller type is handheld.
  245. ui->radioDocked->setEnabled(!is_handheld);
  246. ui->radioUndocked->setEnabled(!is_handheld);
  247. ui->radioDocked->setChecked(Settings::IsDockedMode());
  248. ui->radioUndocked->setChecked(!Settings::IsDockedMode());
  249. // Also force into undocked mode if the controller type is handheld.
  250. if (is_handheld) {
  251. ui->radioUndocked->setChecked(true);
  252. }
  253. }
  254. void ConfigureInput::UpdateAllInputDevices() {
  255. for (const auto& player : player_controllers) {
  256. player->UpdateInputDeviceCombobox();
  257. }
  258. }
  259. void ConfigureInput::UpdateAllInputProfiles(std::size_t player_index) {
  260. for (std::size_t i = 0; i < player_controllers.size(); ++i) {
  261. if (i == player_index) {
  262. continue;
  263. }
  264. player_controllers[i]->UpdateInputProfiles();
  265. }
  266. }