configure_input.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // Copyright 2016 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <algorithm>
  5. #include <memory>
  6. #include <thread>
  7. #include <QSignalBlocker>
  8. #include <QTimer>
  9. #include "core/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_debug_controller.h"
  18. #include "yuzu/configuration/configure_input.h"
  19. #include "yuzu/configuration/configure_input_advanced.h"
  20. #include "yuzu/configuration/configure_input_player.h"
  21. #include "yuzu/configuration/configure_motion_touch.h"
  22. #include "yuzu/configuration/configure_mouse_advanced.h"
  23. #include "yuzu/configuration/configure_touchscreen_advanced.h"
  24. #include "yuzu/configuration/configure_vibration.h"
  25. #include "yuzu/configuration/input_profiles.h"
  26. namespace {
  27. template <typename Dialog, typename... Args>
  28. void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
  29. Dialog dialog(&parent, std::forward<Args>(args)...);
  30. const auto res = dialog.exec();
  31. if (res == QDialog::Accepted) {
  32. dialog.ApplyConfiguration();
  33. }
  34. }
  35. } // Anonymous namespace
  36. void OnDockedModeChanged(bool last_state, bool new_state) {
  37. if (last_state == new_state) {
  38. return;
  39. }
  40. Core::System& system{Core::System::GetInstance()};
  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(QWidget* parent)
  59. : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
  60. profiles(std::make_unique<InputProfiles>()) {
  61. ui->setupUi(this);
  62. }
  63. ConfigureInput::~ConfigureInput() = default;
  64. void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
  65. std::size_t max_players) {
  66. player_controllers = {
  67. new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem,
  68. profiles.get()),
  69. new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem,
  70. profiles.get()),
  71. new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem,
  72. profiles.get()),
  73. new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem,
  74. profiles.get()),
  75. new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem,
  76. profiles.get()),
  77. new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem,
  78. profiles.get()),
  79. new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem,
  80. profiles.get()),
  81. new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem,
  82. profiles.get()),
  83. };
  84. player_tabs = {
  85. ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4,
  86. ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8,
  87. };
  88. player_connected = {
  89. ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
  90. ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
  91. ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
  92. };
  93. std::array<QLabel*, 8> player_connected_labels = {
  94. ui->label, ui->label_3, ui->label_4, ui->label_5,
  95. ui->label_6, ui->label_7, ui->label_8, ui->label_9,
  96. };
  97. for (std::size_t i = 0; i < player_tabs.size(); ++i) {
  98. player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
  99. player_tabs[i]->layout()->addWidget(player_controllers[i]);
  100. connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) {
  101. if (is_connected) {
  102. for (std::size_t index = 0; index <= i; ++index) {
  103. player_connected[index]->setChecked(is_connected);
  104. }
  105. } else {
  106. for (std::size_t index = i; index < player_tabs.size(); ++index) {
  107. player_connected[index]->setChecked(is_connected);
  108. }
  109. }
  110. });
  111. connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
  112. &ConfigureInput::UpdateAllInputDevices);
  113. connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this,
  114. &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection);
  115. connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) {
  116. player_controllers[i]->ConnectPlayer(state == Qt::Checked);
  117. });
  118. // Remove/hide all the elements that exceed max_players, if applicable.
  119. if (i >= max_players) {
  120. ui->tabWidget->removeTab(static_cast<int>(max_players));
  121. player_connected[i]->hide();
  122. player_connected_labels[i]->hide();
  123. }
  124. }
  125. // Only the first player can choose handheld mode so connect the signal just to player 1
  126. connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged,
  127. [this](bool is_handheld) { UpdateDockedState(is_handheld); });
  128. advanced = new ConfigureInputAdvanced(this);
  129. ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
  130. ui->tabAdvanced->layout()->addWidget(advanced);
  131. connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] {
  132. CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem, profiles.get());
  133. });
  134. connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] {
  135. CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem);
  136. });
  137. connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog,
  138. [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });
  139. connect(advanced, &ConfigureInputAdvanced::CallMotionTouchConfigDialog,
  140. [this, input_subsystem] {
  141. CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
  142. });
  143. connect(ui->vibrationButton, &QPushButton::clicked,
  144. [this] { CallConfigureDialog<ConfigureVibration>(*this); });
  145. connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] {
  146. CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
  147. });
  148. connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
  149. connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
  150. RetranslateUI();
  151. LoadConfiguration();
  152. }
  153. QList<QWidget*> ConfigureInput::GetSubTabs() const {
  154. return {
  155. ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
  156. ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8, ui->tabAdvanced,
  157. };
  158. }
  159. void ConfigureInput::ApplyConfiguration() {
  160. for (auto* controller : player_controllers) {
  161. controller->ApplyConfiguration();
  162. controller->TryDisconnectSelectedController();
  163. }
  164. // This emulates a delay between disconnecting and reconnecting controllers as some games
  165. // do not respond to a change in controller type if it was instantaneous.
  166. using namespace std::chrono_literals;
  167. std::this_thread::sleep_for(150ms);
  168. for (auto* controller : player_controllers) {
  169. controller->TryConnectSelectedController();
  170. }
  171. // This emulates a delay between disconnecting and reconnecting controllers as some games
  172. // do not respond to a change in controller type if it was instantaneous.
  173. std::this_thread::sleep_for(150ms);
  174. advanced->ApplyConfiguration();
  175. const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
  176. Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
  177. OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue());
  178. Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
  179. Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
  180. }
  181. void ConfigureInput::changeEvent(QEvent* event) {
  182. if (event->type() == QEvent::LanguageChange) {
  183. RetranslateUI();
  184. }
  185. QWidget::changeEvent(event);
  186. }
  187. void ConfigureInput::RetranslateUI() {
  188. ui->retranslateUi(this);
  189. }
  190. void ConfigureInput::LoadConfiguration() {
  191. LoadPlayerControllerIndices();
  192. UpdateDockedState(Settings::values.players.GetValue()[8].connected);
  193. ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
  194. ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
  195. }
  196. void ConfigureInput::LoadPlayerControllerIndices() {
  197. for (std::size_t i = 0; i < player_connected.size(); ++i) {
  198. const auto connected = Settings::values.players.GetValue()[i].connected ||
  199. (i == 0 && Settings::values.players.GetValue()[8].connected);
  200. player_connected[i]->setChecked(connected);
  201. }
  202. }
  203. void ConfigureInput::ClearAll() {
  204. // We don't have a good way to know what tab is active, but we can find out by getting the
  205. // parent of the consoleInputSettings
  206. auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent());
  207. player_tab->ClearAll();
  208. }
  209. void ConfigureInput::RestoreDefaults() {
  210. // We don't have a good way to know what tab is active, but we can find out by getting the
  211. // parent of the consoleInputSettings
  212. auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent());
  213. player_tab->RestoreDefaults();
  214. ui->radioDocked->setChecked(true);
  215. ui->radioUndocked->setChecked(false);
  216. ui->vibrationGroup->setChecked(true);
  217. ui->motionGroup->setChecked(true);
  218. }
  219. void ConfigureInput::UpdateDockedState(bool is_handheld) {
  220. // Disallow changing the console mode if the controller type is handheld.
  221. ui->radioDocked->setEnabled(!is_handheld);
  222. ui->radioUndocked->setEnabled(!is_handheld);
  223. ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
  224. ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
  225. // Also force into undocked mode if the controller type is handheld.
  226. if (is_handheld) {
  227. ui->radioUndocked->setChecked(true);
  228. }
  229. }
  230. void ConfigureInput::UpdateAllInputDevices() {
  231. for (const auto& player : player_controllers) {
  232. player->UpdateInputDeviceCombobox();
  233. }
  234. }
  235. void ConfigureInput::UpdateAllInputProfiles(std::size_t player_index) {
  236. for (std::size_t i = 0; i < player_controllers.size(); ++i) {
  237. if (i == player_index) {
  238. continue;
  239. }
  240. player_controllers[i]->UpdateInputProfiles();
  241. }
  242. }