configure_input.cpp 13 KB

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