configure_input_player.cpp 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539
  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 <utility>
  7. #include <QGridLayout>
  8. #include <QInputDialog>
  9. #include <QKeyEvent>
  10. #include <QMenu>
  11. #include <QMessageBox>
  12. #include <QTimer>
  13. #include "common/param_package.h"
  14. #include "core/core.h"
  15. #include "core/hle/service/hid/controllers/npad.h"
  16. #include "core/hle/service/hid/hid.h"
  17. #include "core/hle/service/sm/sm.h"
  18. #include "input_common/gcadapter/gc_poller.h"
  19. #include "input_common/main.h"
  20. #include "input_common/mouse/mouse_poller.h"
  21. #include "input_common/udp/udp.h"
  22. #include "ui_configure_input_player.h"
  23. #include "yuzu/bootmanager.h"
  24. #include "yuzu/configuration/config.h"
  25. #include "yuzu/configuration/configure_input_player.h"
  26. #include "yuzu/configuration/configure_input_player_widget.h"
  27. #include "yuzu/configuration/configure_vibration.h"
  28. #include "yuzu/configuration/input_profiles.h"
  29. #include "yuzu/util/limitable_input_dialog.h"
  30. using namespace Service::HID;
  31. const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
  32. ConfigureInputPlayer::analog_sub_buttons{{
  33. "up",
  34. "down",
  35. "left",
  36. "right",
  37. }};
  38. namespace {
  39. constexpr std::size_t HANDHELD_INDEX = 8;
  40. void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
  41. bool connected) {
  42. Core::System& system{Core::System::GetInstance()};
  43. if (!system.IsPoweredOn()) {
  44. return;
  45. }
  46. Service::SM::ServiceManager& sm = system.ServiceManager();
  47. auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>(
  48. HidController::NPad);
  49. npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
  50. }
  51. QString GetKeyName(int key_code) {
  52. switch (key_code) {
  53. case Qt::LeftButton:
  54. return QObject::tr("Click 0");
  55. case Qt::RightButton:
  56. return QObject::tr("Click 1");
  57. case Qt::MiddleButton:
  58. return QObject::tr("Click 2");
  59. case Qt::BackButton:
  60. return QObject::tr("Click 3");
  61. case Qt::ForwardButton:
  62. return QObject::tr("Click 4");
  63. case Qt::Key_Shift:
  64. return QObject::tr("Shift");
  65. case Qt::Key_Control:
  66. return QObject::tr("Ctrl");
  67. case Qt::Key_Alt:
  68. return QObject::tr("Alt");
  69. case Qt::Key_Meta:
  70. return {};
  71. default:
  72. return QKeySequence(key_code).toString();
  73. }
  74. }
  75. void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
  76. const std::string& button_name) {
  77. // The poller returned a complete axis, so set all the buttons
  78. if (input_param.Has("axis_x") && input_param.Has("axis_y")) {
  79. analog_param = input_param;
  80. return;
  81. }
  82. // Check if the current configuration has either no engine or an axis binding.
  83. // Clears out the old binding and adds one with analog_from_button.
  84. if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) {
  85. analog_param = {
  86. {"engine", "analog_from_button"},
  87. };
  88. }
  89. analog_param.Set(button_name, input_param.Serialize());
  90. }
  91. QString ButtonToText(const Common::ParamPackage& param) {
  92. if (!param.Has("engine")) {
  93. return QObject::tr("[not set]");
  94. }
  95. if (param.Get("engine", "") == "keyboard") {
  96. const QString button_str = GetKeyName(param.Get("code", 0));
  97. const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
  98. return QObject::tr("%1%2").arg(toggle, button_str);
  99. }
  100. if (param.Get("engine", "") == "gcpad") {
  101. if (param.Has("axis")) {
  102. const QString axis_str = QString::fromStdString(param.Get("axis", ""));
  103. const QString direction_str = QString::fromStdString(param.Get("direction", ""));
  104. return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str);
  105. }
  106. if (param.Has("button")) {
  107. const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
  108. return QObject::tr("GC Button %1").arg(button_str);
  109. }
  110. return GetKeyName(param.Get("code", 0));
  111. }
  112. if (param.Get("engine", "") == "cemuhookudp") {
  113. if (param.Has("pad_index")) {
  114. const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
  115. return QObject::tr("Motion %1").arg(motion_str);
  116. }
  117. return GetKeyName(param.Get("code", 0));
  118. }
  119. if (param.Get("engine", "") == "sdl") {
  120. if (param.Has("hat")) {
  121. const QString hat_str = QString::fromStdString(param.Get("hat", ""));
  122. const QString direction_str = QString::fromStdString(param.Get("direction", ""));
  123. return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
  124. }
  125. if (param.Has("axis")) {
  126. const QString axis_str = QString::fromStdString(param.Get("axis", ""));
  127. const QString direction_str = QString::fromStdString(param.Get("direction", ""));
  128. return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
  129. }
  130. if (param.Has("button")) {
  131. const QString button_str = QString::fromStdString(param.Get("button", ""));
  132. const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
  133. return QObject::tr("%1Button %2").arg(toggle, button_str);
  134. }
  135. if (param.Has("motion")) {
  136. return QObject::tr("SDL Motion");
  137. }
  138. return {};
  139. }
  140. if (param.Get("engine", "") == "mouse") {
  141. if (param.Has("button")) {
  142. const QString button_str = QString::number(int(param.Get("button", 0)));
  143. const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
  144. return QObject::tr("%1Click %2").arg(toggle, button_str);
  145. }
  146. return GetKeyName(param.Get("code", 0));
  147. }
  148. return QObject::tr("[unknown]");
  149. }
  150. QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
  151. if (!param.Has("engine")) {
  152. return QObject::tr("[not set]");
  153. }
  154. if (param.Get("engine", "") == "analog_from_button") {
  155. return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
  156. }
  157. const auto engine_str = param.Get("engine", "");
  158. const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
  159. const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
  160. const bool invert_x = param.Get("invert_x", "+") == "-";
  161. const bool invert_y = param.Get("invert_y", "+") == "-";
  162. if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse") {
  163. if (dir == "modifier") {
  164. return QObject::tr("[unused]");
  165. }
  166. if (dir == "left") {
  167. const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-");
  168. return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
  169. }
  170. if (dir == "right") {
  171. const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+");
  172. return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
  173. }
  174. if (dir == "up") {
  175. const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+");
  176. return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
  177. }
  178. if (dir == "down") {
  179. const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-");
  180. return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
  181. }
  182. return {};
  183. }
  184. return QObject::tr("[unknown]");
  185. }
  186. } // namespace
  187. ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index,
  188. QWidget* bottom_row,
  189. InputCommon::InputSubsystem* input_subsystem_,
  190. InputProfiles* profiles_, bool debug)
  191. : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
  192. debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_),
  193. timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()),
  194. bottom_row(bottom_row) {
  195. ui->setupUi(this);
  196. setFocusPolicy(Qt::ClickFocus);
  197. button_map = {
  198. ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY,
  199. ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR,
  200. ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus,
  201. ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown,
  202. ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot,
  203. };
  204. analog_map_buttons = {{
  205. {
  206. ui->buttonLStickUp,
  207. ui->buttonLStickDown,
  208. ui->buttonLStickLeft,
  209. ui->buttonLStickRight,
  210. },
  211. {
  212. ui->buttonRStickUp,
  213. ui->buttonRStickDown,
  214. ui->buttonRStickLeft,
  215. ui->buttonRStickRight,
  216. },
  217. }};
  218. motion_map = {
  219. ui->buttonMotionLeft,
  220. ui->buttonMotionRight,
  221. };
  222. analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone};
  223. analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone};
  224. analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup};
  225. analog_map_modifier_button = {ui->buttonLStickMod, ui->buttonRStickMod};
  226. analog_map_modifier_label = {ui->labelLStickModifierRange, ui->labelRStickModifierRange};
  227. analog_map_modifier_slider = {ui->sliderLStickModifierRange, ui->sliderRStickModifierRange};
  228. analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup};
  229. analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange};
  230. const auto ConfigureButtonClick = [&](QPushButton* button, std::size_t button_id,
  231. Common::ParamPackage* param, int default_val,
  232. InputCommon::Polling::DeviceType type) {
  233. connect(button, &QPushButton::clicked, [=, this] {
  234. HandleClick(
  235. button, button_id,
  236. [=, this](Common::ParamPackage params) {
  237. // Workaround for ZL & ZR for analog triggers like on XBOX
  238. // controllers. Analog triggers (from controllers like the XBOX
  239. // controller) would not work due to a different range of their
  240. // signals (from 0 to 255 on analog triggers instead of -32768 to
  241. // 32768 on analog joysticks). The SDL driver misinterprets analog
  242. // triggers as analog joysticks.
  243. // TODO: reinterpret the signal range for analog triggers to map the
  244. // values correctly. This is required for the correct emulation of
  245. // the analog triggers of the GameCube controller.
  246. if (button == ui->buttonZL || button == ui->buttonZR) {
  247. params.Set("direction", "+");
  248. params.Set("threshold", "0.5");
  249. }
  250. *param = std::move(params);
  251. },
  252. type);
  253. });
  254. };
  255. for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
  256. auto* const button = button_map[button_id];
  257. if (button == nullptr) {
  258. continue;
  259. }
  260. ConfigureButtonClick(button_map[button_id], button_id, &buttons_param[button_id],
  261. Config::default_buttons[button_id],
  262. InputCommon::Polling::DeviceType::Button);
  263. button->setContextMenuPolicy(Qt::CustomContextMenu);
  264. connect(button, &QPushButton::customContextMenuRequested,
  265. [=, this](const QPoint& menu_location) {
  266. QMenu context_menu;
  267. context_menu.addAction(tr("Clear"), [&] {
  268. buttons_param[button_id].Clear();
  269. button_map[button_id]->setText(tr("[not set]"));
  270. });
  271. context_menu.addAction(tr("Toggle button"), [&] {
  272. const bool toggle_value = !buttons_param[button_id].Get("toggle", false);
  273. buttons_param[button_id].Set("toggle", toggle_value);
  274. button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
  275. });
  276. if (buttons_param[button_id].Has("threshold")) {
  277. context_menu.addAction(tr("Set threshold"), [&] {
  278. const int button_threshold = static_cast<int>(
  279. buttons_param[button_id].Get("threshold", 0.5f) * 100.0f);
  280. const int new_threshold = QInputDialog::getInt(
  281. this, tr("Set threshold"), tr("Choose a value between 0% and 100%"),
  282. button_threshold, 0, 100);
  283. buttons_param[button_id].Set("threshold", new_threshold / 100.0f);
  284. if (button_id == Settings::NativeButton::ZL) {
  285. ui->sliderZLThreshold->setValue(new_threshold);
  286. }
  287. if (button_id == Settings::NativeButton::ZR) {
  288. ui->sliderZRThreshold->setValue(new_threshold);
  289. }
  290. });
  291. }
  292. context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
  293. ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
  294. });
  295. }
  296. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  297. auto* const button = motion_map[motion_id];
  298. if (button == nullptr) {
  299. continue;
  300. }
  301. ConfigureButtonClick(motion_map[motion_id], motion_id, &motions_param[motion_id],
  302. Config::default_motions[motion_id],
  303. InputCommon::Polling::DeviceType::Motion);
  304. button->setContextMenuPolicy(Qt::CustomContextMenu);
  305. connect(button, &QPushButton::customContextMenuRequested,
  306. [=, this](const QPoint& menu_location) {
  307. QMenu context_menu;
  308. context_menu.addAction(tr("Clear"), [&] {
  309. motions_param[motion_id].Clear();
  310. motion_map[motion_id]->setText(tr("[not set]"));
  311. });
  312. context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location));
  313. });
  314. }
  315. connect(ui->sliderZLThreshold, &QSlider::valueChanged, [=, this] {
  316. if (buttons_param[Settings::NativeButton::ZL].Has("threshold")) {
  317. const auto slider_value = ui->sliderZLThreshold->value();
  318. buttons_param[Settings::NativeButton::ZL].Set("threshold", slider_value / 100.0f);
  319. }
  320. });
  321. connect(ui->sliderZRThreshold, &QSlider::valueChanged, [=, this] {
  322. if (buttons_param[Settings::NativeButton::ZR].Has("threshold")) {
  323. const auto slider_value = ui->sliderZRThreshold->value();
  324. buttons_param[Settings::NativeButton::ZR].Set("threshold", slider_value / 100.0f);
  325. }
  326. });
  327. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  328. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  329. auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
  330. if (analog_button == nullptr) {
  331. continue;
  332. }
  333. connect(analog_button, &QPushButton::clicked, [=, this] {
  334. if (!map_analog_stick_accepted) {
  335. map_analog_stick_accepted =
  336. QMessageBox::information(
  337. this, tr("Map Analog Stick"),
  338. tr("After pressing OK, first move your joystick horizontally, and then "
  339. "vertically.\nTo invert the axes, first move your joystick "
  340. "vertically, and then horizontally."),
  341. QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok;
  342. if (!map_analog_stick_accepted) {
  343. return;
  344. }
  345. }
  346. HandleClick(
  347. analog_map_buttons[analog_id][sub_button_id], analog_id,
  348. [=, this](const Common::ParamPackage& params) {
  349. SetAnalogParam(params, analogs_param[analog_id],
  350. analog_sub_buttons[sub_button_id]);
  351. },
  352. InputCommon::Polling::DeviceType::AnalogPreferred);
  353. });
  354. analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
  355. connect(
  356. analog_button, &QPushButton::customContextMenuRequested,
  357. [=, this](const QPoint& menu_location) {
  358. QMenu context_menu;
  359. context_menu.addAction(tr("Clear"), [&] {
  360. analogs_param[analog_id].Clear();
  361. analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
  362. });
  363. context_menu.addAction(tr("Invert axis"), [&] {
  364. if (sub_button_id == 2 || sub_button_id == 3) {
  365. const bool invert_value =
  366. analogs_param[analog_id].Get("invert_x", "+") == "-";
  367. const std::string invert_str = invert_value ? "+" : "-";
  368. analogs_param[analog_id].Set("invert_x", invert_str);
  369. }
  370. if (sub_button_id == 0 || sub_button_id == 1) {
  371. const bool invert_value =
  372. analogs_param[analog_id].Get("invert_y", "+") == "-";
  373. const std::string invert_str = invert_value ? "+" : "-";
  374. analogs_param[analog_id].Set("invert_y", invert_str);
  375. }
  376. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM;
  377. ++sub_button_id) {
  378. analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
  379. analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
  380. }
  381. });
  382. context_menu.exec(
  383. analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(menu_location));
  384. ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
  385. });
  386. }
  387. // Handle clicks for the modifier buttons as well.
  388. connect(analog_map_modifier_button[analog_id], &QPushButton::clicked, [=, this] {
  389. HandleClick(
  390. analog_map_modifier_button[analog_id], analog_id,
  391. [=, this](const Common::ParamPackage& params) {
  392. analogs_param[analog_id].Set("modifier", params.Serialize());
  393. },
  394. InputCommon::Polling::DeviceType::Button);
  395. });
  396. analog_map_modifier_button[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu);
  397. connect(analog_map_modifier_button[analog_id], &QPushButton::customContextMenuRequested,
  398. [=, this](const QPoint& menu_location) {
  399. QMenu context_menu;
  400. context_menu.addAction(tr("Clear"), [&] {
  401. analogs_param[analog_id].Set("modifier", "");
  402. analog_map_modifier_button[analog_id]->setText(tr("[not set]"));
  403. });
  404. context_menu.addAction(tr("Toggle button"), [&] {
  405. Common::ParamPackage modifier_param =
  406. Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")};
  407. const bool toggle_value = !modifier_param.Get("toggle", false);
  408. modifier_param.Set("toggle", toggle_value);
  409. analogs_param[analog_id].Set("modifier", modifier_param.Serialize());
  410. analog_map_modifier_button[analog_id]->setText(
  411. ButtonToText(modifier_param));
  412. });
  413. context_menu.exec(
  414. analog_map_modifier_button[analog_id]->mapToGlobal(menu_location));
  415. });
  416. connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged),
  417. [=, this] {
  418. const auto spinbox_value = analog_map_range_spinbox[analog_id]->value();
  419. analogs_param[analog_id].Set("range", spinbox_value / 100.0f);
  420. ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
  421. });
  422. connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] {
  423. const auto slider_value = analog_map_deadzone_slider[analog_id]->value();
  424. analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value));
  425. analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
  426. ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
  427. });
  428. connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] {
  429. const auto slider_value = analog_map_modifier_slider[analog_id]->value();
  430. analog_map_modifier_label[analog_id]->setText(
  431. tr("Modifier Range: %1%").arg(slider_value));
  432. analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
  433. });
  434. }
  435. // Player Connected checkbox
  436. connect(ui->groupConnectedController, &QGroupBox::toggled, [this](bool checked) {
  437. emit Connected(checked);
  438. ui->controllerFrame->SetConnectedStatus(checked);
  439. });
  440. if (player_index == 0) {
  441. connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged),
  442. [this](int index) {
  443. emit HandheldStateChanged(GetControllerTypeFromIndex(index) ==
  444. Settings::ControllerType::Handheld);
  445. });
  446. }
  447. if (debug || player_index == 9) {
  448. ui->groupConnectedController->setCheckable(false);
  449. }
  450. // The Debug Controller can only choose the Pro Controller.
  451. if (debug) {
  452. ui->buttonScreenshot->setEnabled(false);
  453. ui->buttonHome->setEnabled(false);
  454. ui->comboControllerType->addItem(tr("Pro Controller"));
  455. } else {
  456. SetConnectableControllers();
  457. }
  458. UpdateControllerIcon();
  459. UpdateControllerAvailableButtons();
  460. UpdateControllerEnabledButtons();
  461. UpdateControllerButtonNames();
  462. UpdateMotionButtons();
  463. connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
  464. UpdateControllerIcon();
  465. UpdateControllerAvailableButtons();
  466. UpdateControllerEnabledButtons();
  467. UpdateControllerButtonNames();
  468. UpdateMotionButtons();
  469. });
  470. connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
  471. &ConfigureInputPlayer::UpdateMappingWithDefaults);
  472. ui->comboDevices->setCurrentIndex(-1);
  473. ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
  474. connect(ui->buttonRefreshDevices, &QPushButton::clicked,
  475. [this] { emit RefreshInputDevices(); });
  476. timeout_timer->setSingleShot(true);
  477. connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
  478. connect(poll_timer.get(), &QTimer::timeout, [this] {
  479. Common::ParamPackage params;
  480. if (input_subsystem->GetGCButtons()->IsPolling()) {
  481. params = input_subsystem->GetGCButtons()->GetNextInput();
  482. if (params.Has("engine") && IsInputAcceptable(params)) {
  483. SetPollingResult(params, false);
  484. return;
  485. }
  486. }
  487. if (input_subsystem->GetGCAnalogs()->IsPolling()) {
  488. params = input_subsystem->GetGCAnalogs()->GetNextInput();
  489. if (params.Has("engine") && IsInputAcceptable(params)) {
  490. SetPollingResult(params, false);
  491. return;
  492. }
  493. }
  494. if (input_subsystem->GetUDPMotions()->IsPolling()) {
  495. params = input_subsystem->GetUDPMotions()->GetNextInput();
  496. if (params.Has("engine")) {
  497. SetPollingResult(params, false);
  498. return;
  499. }
  500. }
  501. if (input_subsystem->GetMouseButtons()->IsPolling()) {
  502. params = input_subsystem->GetMouseButtons()->GetNextInput();
  503. if (params.Has("engine") && IsInputAcceptable(params)) {
  504. SetPollingResult(params, false);
  505. return;
  506. }
  507. }
  508. if (input_subsystem->GetMouseAnalogs()->IsPolling()) {
  509. params = input_subsystem->GetMouseAnalogs()->GetNextInput();
  510. if (params.Has("engine") && IsInputAcceptable(params)) {
  511. SetPollingResult(params, false);
  512. return;
  513. }
  514. }
  515. if (input_subsystem->GetMouseMotions()->IsPolling()) {
  516. params = input_subsystem->GetMouseMotions()->GetNextInput();
  517. if (params.Has("engine") && IsInputAcceptable(params)) {
  518. SetPollingResult(params, false);
  519. return;
  520. }
  521. }
  522. if (input_subsystem->GetMouseTouch()->IsPolling()) {
  523. params = input_subsystem->GetMouseTouch()->GetNextInput();
  524. if (params.Has("engine") && IsInputAcceptable(params)) {
  525. SetPollingResult(params, false);
  526. return;
  527. }
  528. }
  529. for (auto& poller : device_pollers) {
  530. params = poller->GetNextInput();
  531. if (params.Has("engine") && IsInputAcceptable(params)) {
  532. SetPollingResult(params, false);
  533. return;
  534. }
  535. }
  536. });
  537. UpdateInputProfiles();
  538. connect(ui->buttonProfilesNew, &QPushButton::clicked, this,
  539. &ConfigureInputPlayer::CreateProfile);
  540. connect(ui->buttonProfilesDelete, &QPushButton::clicked, this,
  541. &ConfigureInputPlayer::DeleteProfile);
  542. connect(ui->comboProfiles, qOverload<int>(&QComboBox::activated), this,
  543. &ConfigureInputPlayer::LoadProfile);
  544. connect(ui->buttonProfilesSave, &QPushButton::clicked, this,
  545. &ConfigureInputPlayer::SaveProfile);
  546. LoadConfiguration();
  547. ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
  548. ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked());
  549. }
  550. ConfigureInputPlayer::~ConfigureInputPlayer() = default;
  551. void ConfigureInputPlayer::ApplyConfiguration() {
  552. auto& player = Settings::values.players.GetValue()[player_index];
  553. auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons;
  554. auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs;
  555. std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(),
  556. [](const Common::ParamPackage& param) { return param.Serialize(); });
  557. std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(),
  558. [](const Common::ParamPackage& param) { return param.Serialize(); });
  559. if (debug) {
  560. return;
  561. }
  562. auto& motions = player.motions;
  563. std::transform(motions_param.begin(), motions_param.end(), motions.begin(),
  564. [](const Common::ParamPackage& param) { return param.Serialize(); });
  565. // Apply configuration for handheld
  566. if (player_index == 0) {
  567. auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
  568. const auto handheld_connected = handheld.connected;
  569. handheld = player;
  570. handheld.connected = handheld_connected;
  571. }
  572. }
  573. void ConfigureInputPlayer::TryConnectSelectedController() {
  574. auto& player = Settings::values.players.GetValue()[player_index];
  575. const auto controller_type =
  576. GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
  577. const auto player_connected = ui->groupConnectedController->isChecked() &&
  578. controller_type != Settings::ControllerType::Handheld;
  579. // Connect Handheld depending on Player 1's controller configuration.
  580. if (player_index == 0) {
  581. auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
  582. const auto handheld_connected = ui->groupConnectedController->isChecked() &&
  583. controller_type == Settings::ControllerType::Handheld;
  584. // Connect only if handheld is going from disconnected to connected
  585. if (!handheld.connected && handheld_connected) {
  586. UpdateController(controller_type, HANDHELD_INDEX, true);
  587. }
  588. handheld.connected = handheld_connected;
  589. }
  590. if (player.controller_type == controller_type && player.connected == player_connected) {
  591. // Set vibration devices in the event that the input device has changed.
  592. ConfigureVibration::SetVibrationDevices(player_index);
  593. return;
  594. }
  595. player.controller_type = controller_type;
  596. player.connected = player_connected;
  597. ConfigureVibration::SetVibrationDevices(player_index);
  598. if (!player.connected) {
  599. return;
  600. }
  601. UpdateController(controller_type, player_index, true);
  602. }
  603. void ConfigureInputPlayer::TryDisconnectSelectedController() {
  604. const auto& player = Settings::values.players.GetValue()[player_index];
  605. const auto controller_type =
  606. GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
  607. const auto player_connected = ui->groupConnectedController->isChecked() &&
  608. controller_type != Settings::ControllerType::Handheld;
  609. // Disconnect Handheld depending on Player 1's controller configuration.
  610. if (player_index == 0 && player.controller_type == Settings::ControllerType::Handheld) {
  611. const auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
  612. const auto handheld_connected = ui->groupConnectedController->isChecked() &&
  613. controller_type == Settings::ControllerType::Handheld;
  614. // Disconnect only if handheld is going from connected to disconnected
  615. if (handheld.connected && !handheld_connected) {
  616. UpdateController(controller_type, HANDHELD_INDEX, false);
  617. }
  618. return;
  619. }
  620. // Do not do anything if the controller configuration has not changed.
  621. if (player.controller_type == controller_type && player.connected == player_connected) {
  622. return;
  623. }
  624. // Do not disconnect if the controller is already disconnected
  625. if (!player.connected) {
  626. return;
  627. }
  628. // Disconnect the controller first.
  629. UpdateController(controller_type, player_index, false);
  630. }
  631. void ConfigureInputPlayer::showEvent(QShowEvent* event) {
  632. if (bottom_row == nullptr) {
  633. return;
  634. }
  635. QWidget::showEvent(event);
  636. ui->main->addWidget(bottom_row);
  637. }
  638. void ConfigureInputPlayer::changeEvent(QEvent* event) {
  639. if (event->type() == QEvent::LanguageChange) {
  640. RetranslateUI();
  641. }
  642. QWidget::changeEvent(event);
  643. }
  644. void ConfigureInputPlayer::RetranslateUI() {
  645. ui->retranslateUi(this);
  646. UpdateUI();
  647. }
  648. void ConfigureInputPlayer::LoadConfiguration() {
  649. auto& player = Settings::values.players.GetValue()[player_index];
  650. if (debug) {
  651. std::transform(Settings::values.debug_pad_buttons.begin(),
  652. Settings::values.debug_pad_buttons.end(), buttons_param.begin(),
  653. [](const std::string& str) { return Common::ParamPackage(str); });
  654. std::transform(Settings::values.debug_pad_analogs.begin(),
  655. Settings::values.debug_pad_analogs.end(), analogs_param.begin(),
  656. [](const std::string& str) { return Common::ParamPackage(str); });
  657. } else {
  658. std::transform(player.buttons.begin(), player.buttons.end(), buttons_param.begin(),
  659. [](const std::string& str) { return Common::ParamPackage(str); });
  660. std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(),
  661. [](const std::string& str) { return Common::ParamPackage(str); });
  662. std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(),
  663. [](const std::string& str) { return Common::ParamPackage(str); });
  664. }
  665. UpdateUI();
  666. UpdateInputDeviceCombobox();
  667. if (debug) {
  668. return;
  669. }
  670. ui->comboControllerType->setCurrentIndex(GetIndexFromControllerType(player.controller_type));
  671. ui->groupConnectedController->setChecked(
  672. player.connected ||
  673. (player_index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected));
  674. }
  675. void ConfigureInputPlayer::ConnectPlayer(bool connected) {
  676. ui->groupConnectedController->setChecked(connected);
  677. }
  678. void ConfigureInputPlayer::UpdateInputDeviceCombobox() {
  679. // Skip input device persistence if "Input Devices" is set to "Any".
  680. if (ui->comboDevices->currentIndex() == 0) {
  681. UpdateInputDevices();
  682. return;
  683. }
  684. // Find the first button that isn't empty.
  685. const auto button_param =
  686. std::find_if(buttons_param.begin(), buttons_param.end(),
  687. [](const Common::ParamPackage param) { return param.Has("engine"); });
  688. const bool buttons_empty = button_param == buttons_param.end();
  689. const auto current_engine = button_param->Get("engine", "");
  690. const auto current_guid = button_param->Get("guid", "");
  691. const auto current_port = button_param->Get("port", "");
  692. const bool is_keyboard_mouse = current_engine == "keyboard" || current_engine == "mouse";
  693. UpdateInputDevices();
  694. if (buttons_empty) {
  695. return;
  696. }
  697. const bool all_one_device =
  698. std::all_of(buttons_param.begin(), buttons_param.end(),
  699. [current_engine, current_guid, current_port,
  700. is_keyboard_mouse](const Common::ParamPackage param) {
  701. if (is_keyboard_mouse) {
  702. return !param.Has("engine") || param.Get("engine", "") == "keyboard" ||
  703. param.Get("engine", "") == "mouse";
  704. }
  705. return !param.Has("engine") || (param.Get("engine", "") == current_engine &&
  706. param.Get("guid", "") == current_guid &&
  707. param.Get("port", "") == current_port);
  708. });
  709. if (all_one_device) {
  710. if (is_keyboard_mouse) {
  711. ui->comboDevices->setCurrentIndex(1);
  712. return;
  713. }
  714. const auto devices_it = std::find_if(
  715. input_devices.begin(), input_devices.end(),
  716. [current_engine, current_guid, current_port](const Common::ParamPackage param) {
  717. return param.Get("class", "") == current_engine &&
  718. param.Get("guid", "") == current_guid &&
  719. param.Get("port", "") == current_port;
  720. });
  721. const int device_index =
  722. devices_it != input_devices.end()
  723. ? static_cast<int>(std::distance(input_devices.begin(), devices_it))
  724. : 0;
  725. ui->comboDevices->setCurrentIndex(device_index);
  726. } else {
  727. ui->comboDevices->setCurrentIndex(0);
  728. }
  729. }
  730. void ConfigureInputPlayer::RestoreDefaults() {
  731. UpdateMappingWithDefaults();
  732. }
  733. void ConfigureInputPlayer::ClearAll() {
  734. for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
  735. const auto* const button = button_map[button_id];
  736. if (button == nullptr) {
  737. continue;
  738. }
  739. buttons_param[button_id].Clear();
  740. }
  741. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  742. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  743. const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
  744. if (analog_button == nullptr) {
  745. continue;
  746. }
  747. analogs_param[analog_id].Clear();
  748. }
  749. }
  750. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  751. const auto* const motion_button = motion_map[motion_id];
  752. if (motion_button == nullptr) {
  753. continue;
  754. }
  755. motions_param[motion_id].Clear();
  756. }
  757. UpdateUI();
  758. UpdateInputDevices();
  759. }
  760. void ConfigureInputPlayer::UpdateUI() {
  761. for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) {
  762. button_map[button]->setText(ButtonToText(buttons_param[button]));
  763. }
  764. if (buttons_param[Settings::NativeButton::ZL].Has("threshold")) {
  765. const int button_threshold = static_cast<int>(
  766. buttons_param[Settings::NativeButton::ZL].Get("threshold", 0.5f) * 100.0f);
  767. ui->sliderZLThreshold->setValue(button_threshold);
  768. }
  769. if (buttons_param[Settings::NativeButton::ZR].Has("threshold")) {
  770. const int button_threshold = static_cast<int>(
  771. buttons_param[Settings::NativeButton::ZR].Get("threshold", 0.5f) * 100.0f);
  772. ui->sliderZRThreshold->setValue(button_threshold);
  773. }
  774. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  775. motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id]));
  776. }
  777. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  778. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  779. auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
  780. if (analog_button == nullptr) {
  781. continue;
  782. }
  783. analog_button->setText(
  784. AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
  785. }
  786. analog_map_modifier_button[analog_id]->setText(
  787. ButtonToText(Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")}));
  788. const auto deadzone_label = analog_map_deadzone_label[analog_id];
  789. const auto deadzone_slider = analog_map_deadzone_slider[analog_id];
  790. const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id];
  791. const auto modifier_label = analog_map_modifier_label[analog_id];
  792. const auto modifier_slider = analog_map_modifier_slider[analog_id];
  793. const auto range_groupbox = analog_map_range_groupbox[analog_id];
  794. const auto range_spinbox = analog_map_range_spinbox[analog_id];
  795. int slider_value;
  796. auto& param = analogs_param[analog_id];
  797. const bool is_controller = param.Get("engine", "") == "sdl" ||
  798. param.Get("engine", "") == "gcpad" ||
  799. param.Get("engine", "") == "mouse";
  800. if (is_controller) {
  801. if (!param.Has("deadzone")) {
  802. param.Set("deadzone", 0.1f);
  803. }
  804. slider_value = static_cast<int>(param.Get("deadzone", 0.1f) * 100);
  805. deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
  806. deadzone_slider->setValue(slider_value);
  807. if (!param.Has("range")) {
  808. param.Set("range", 1.0f);
  809. }
  810. range_spinbox->setValue(static_cast<int>(param.Get("range", 1.0f) * 100));
  811. } else {
  812. if (!param.Has("modifier_scale")) {
  813. param.Set("modifier_scale", 0.5f);
  814. }
  815. slider_value = static_cast<int>(param.Get("modifier_scale", 0.5f) * 100);
  816. modifier_label->setText(tr("Modifier Range: %1%").arg(slider_value));
  817. modifier_slider->setValue(slider_value);
  818. }
  819. deadzone_label->setVisible(is_controller);
  820. deadzone_slider->setVisible(is_controller);
  821. modifier_groupbox->setVisible(!is_controller);
  822. modifier_label->setVisible(!is_controller);
  823. modifier_slider->setVisible(!is_controller);
  824. range_groupbox->setVisible(is_controller);
  825. ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
  826. }
  827. }
  828. void ConfigureInputPlayer::SetConnectableControllers() {
  829. const auto add_controllers = [this](bool enable_all,
  830. Controller_NPad::NpadStyleSet npad_style_set = {}) {
  831. index_controller_type_pairs.clear();
  832. ui->comboControllerType->clear();
  833. if (enable_all || npad_style_set.fullkey == 1) {
  834. index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
  835. Settings::ControllerType::ProController);
  836. ui->comboControllerType->addItem(tr("Pro Controller"));
  837. }
  838. if (enable_all || npad_style_set.joycon_dual == 1) {
  839. index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
  840. Settings::ControllerType::DualJoyconDetached);
  841. ui->comboControllerType->addItem(tr("Dual Joycons"));
  842. }
  843. if (enable_all || npad_style_set.joycon_left == 1) {
  844. index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
  845. Settings::ControllerType::LeftJoycon);
  846. ui->comboControllerType->addItem(tr("Left Joycon"));
  847. }
  848. if (enable_all || npad_style_set.joycon_right == 1) {
  849. index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
  850. Settings::ControllerType::RightJoycon);
  851. ui->comboControllerType->addItem(tr("Right Joycon"));
  852. }
  853. if (player_index == 0 && (enable_all || npad_style_set.handheld == 1)) {
  854. index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
  855. Settings::ControllerType::Handheld);
  856. ui->comboControllerType->addItem(tr("Handheld"));
  857. }
  858. if (enable_all || npad_style_set.gamecube == 1) {
  859. index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
  860. Settings::ControllerType::GameCube);
  861. ui->comboControllerType->addItem(tr("GameCube Controller"));
  862. }
  863. };
  864. Core::System& system{Core::System::GetInstance()};
  865. if (!system.IsPoweredOn()) {
  866. add_controllers(true);
  867. return;
  868. }
  869. Service::SM::ServiceManager& sm = system.ServiceManager();
  870. auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>(
  871. HidController::NPad);
  872. add_controllers(false, npad.GetSupportedStyleSet());
  873. }
  874. Settings::ControllerType ConfigureInputPlayer::GetControllerTypeFromIndex(int index) const {
  875. const auto it =
  876. std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(),
  877. [index](const auto& pair) { return pair.first == index; });
  878. if (it == index_controller_type_pairs.end()) {
  879. return Settings::ControllerType::ProController;
  880. }
  881. return it->second;
  882. }
  883. int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType type) const {
  884. const auto it =
  885. std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(),
  886. [type](const auto& pair) { return pair.second == type; });
  887. if (it == index_controller_type_pairs.end()) {
  888. return -1;
  889. }
  890. return it->first;
  891. }
  892. void ConfigureInputPlayer::UpdateInputDevices() {
  893. input_devices = input_subsystem->GetInputDevices();
  894. ui->comboDevices->clear();
  895. for (auto device : input_devices) {
  896. ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
  897. }
  898. }
  899. void ConfigureInputPlayer::UpdateControllerIcon() {
  900. // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its
  901. // "nonstandard" to use an image through the icon support)
  902. const QString stylesheet = [this] {
  903. switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
  904. case Settings::ControllerType::ProController:
  905. return QStringLiteral("image: url(:/controller/pro_controller%0)");
  906. case Settings::ControllerType::DualJoyconDetached:
  907. return QStringLiteral("image: url(:/controller/dual_joycon%0)");
  908. case Settings::ControllerType::LeftJoycon:
  909. return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)");
  910. case Settings::ControllerType::RightJoycon:
  911. return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)");
  912. case Settings::ControllerType::Handheld:
  913. return QStringLiteral("image: url(:/controller/handheld%0)");
  914. default:
  915. return QString{};
  916. }
  917. }();
  918. const QString theme = [] {
  919. if (QIcon::themeName().contains(QStringLiteral("dark"))) {
  920. return QStringLiteral("_dark");
  921. } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
  922. return QStringLiteral("_midnight");
  923. } else {
  924. return QString{};
  925. }
  926. }();
  927. ui->controllerFrame->SetControllerType(
  928. GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()));
  929. }
  930. void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
  931. auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
  932. if (debug) {
  933. layout = Settings::ControllerType::ProController;
  934. }
  935. // List of all the widgets that will be hidden by any of the following layouts that need
  936. // "unhidden" after the controller type changes
  937. const std::array<QWidget*, 11> layout_show = {
  938. ui->buttonShoulderButtonsSLSR,
  939. ui->horizontalSpacerShoulderButtonsWidget,
  940. ui->horizontalSpacerShoulderButtonsWidget2,
  941. ui->buttonShoulderButtonsLeft,
  942. ui->buttonMiscButtonsMinusScreenshot,
  943. ui->bottomLeft,
  944. ui->buttonShoulderButtonsRight,
  945. ui->buttonMiscButtonsPlusHome,
  946. ui->bottomRight,
  947. ui->buttonMiscButtonsMinusGroup,
  948. ui->buttonMiscButtonsScreenshotGroup,
  949. };
  950. for (auto* widget : layout_show) {
  951. widget->show();
  952. }
  953. std::vector<QWidget*> layout_hidden;
  954. switch (layout) {
  955. case Settings::ControllerType::ProController:
  956. case Settings::ControllerType::DualJoyconDetached:
  957. case Settings::ControllerType::Handheld:
  958. layout_hidden = {
  959. ui->buttonShoulderButtonsSLSR,
  960. ui->horizontalSpacerShoulderButtonsWidget2,
  961. };
  962. break;
  963. case Settings::ControllerType::LeftJoycon:
  964. layout_hidden = {
  965. ui->horizontalSpacerShoulderButtonsWidget2,
  966. ui->buttonShoulderButtonsRight,
  967. ui->buttonMiscButtonsPlusHome,
  968. ui->bottomRight,
  969. };
  970. break;
  971. case Settings::ControllerType::RightJoycon:
  972. layout_hidden = {
  973. ui->horizontalSpacerShoulderButtonsWidget,
  974. ui->buttonShoulderButtonsLeft,
  975. ui->buttonMiscButtonsMinusScreenshot,
  976. ui->bottomLeft,
  977. };
  978. break;
  979. case Settings::ControllerType::GameCube:
  980. layout_hidden = {
  981. ui->buttonShoulderButtonsSLSR,
  982. ui->horizontalSpacerShoulderButtonsWidget2,
  983. ui->buttonMiscButtonsMinusGroup,
  984. ui->buttonMiscButtonsScreenshotGroup,
  985. };
  986. break;
  987. }
  988. for (auto* widget : layout_hidden) {
  989. widget->hide();
  990. }
  991. }
  992. void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
  993. auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
  994. if (debug) {
  995. layout = Settings::ControllerType::ProController;
  996. }
  997. // List of all the widgets that will be disabled by any of the following layouts that need
  998. // "enabled" after the controller type changes
  999. const std::array<QWidget*, 4> layout_enable = {
  1000. ui->buttonHome,
  1001. ui->buttonLStickPressedGroup,
  1002. ui->groupRStickPressed,
  1003. ui->buttonShoulderButtonsButtonLGroup,
  1004. };
  1005. for (auto* widget : layout_enable) {
  1006. widget->setEnabled(true);
  1007. }
  1008. std::vector<QWidget*> layout_disable;
  1009. switch (layout) {
  1010. case Settings::ControllerType::ProController:
  1011. case Settings::ControllerType::DualJoyconDetached:
  1012. case Settings::ControllerType::Handheld:
  1013. case Settings::ControllerType::LeftJoycon:
  1014. case Settings::ControllerType::RightJoycon:
  1015. // TODO(wwylele): enable this when we actually emulate it
  1016. layout_disable = {
  1017. ui->buttonHome,
  1018. };
  1019. break;
  1020. case Settings::ControllerType::GameCube:
  1021. layout_disable = {
  1022. ui->buttonHome,
  1023. ui->buttonLStickPressedGroup,
  1024. ui->groupRStickPressed,
  1025. ui->buttonShoulderButtonsButtonLGroup,
  1026. };
  1027. break;
  1028. }
  1029. for (auto* widget : layout_disable) {
  1030. widget->setEnabled(false);
  1031. }
  1032. }
  1033. void ConfigureInputPlayer::UpdateMotionButtons() {
  1034. if (debug) {
  1035. // Motion isn't used with the debug controller, hide both groupboxes.
  1036. ui->buttonMotionLeftGroup->hide();
  1037. ui->buttonMotionRightGroup->hide();
  1038. return;
  1039. }
  1040. // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
  1041. switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
  1042. case Settings::ControllerType::ProController:
  1043. case Settings::ControllerType::LeftJoycon:
  1044. case Settings::ControllerType::Handheld:
  1045. // Show "Motion 1" and hide "Motion 2".
  1046. ui->buttonMotionLeftGroup->show();
  1047. ui->buttonMotionRightGroup->hide();
  1048. break;
  1049. case Settings::ControllerType::RightJoycon:
  1050. // Show "Motion 2" and hide "Motion 1".
  1051. ui->buttonMotionLeftGroup->hide();
  1052. ui->buttonMotionRightGroup->show();
  1053. break;
  1054. case Settings::ControllerType::GameCube:
  1055. // Hide both "Motion 1/2".
  1056. ui->buttonMotionLeftGroup->hide();
  1057. ui->buttonMotionRightGroup->hide();
  1058. break;
  1059. case Settings::ControllerType::DualJoyconDetached:
  1060. default:
  1061. // Show both "Motion 1/2".
  1062. ui->buttonMotionLeftGroup->show();
  1063. ui->buttonMotionRightGroup->show();
  1064. break;
  1065. }
  1066. }
  1067. void ConfigureInputPlayer::UpdateControllerButtonNames() {
  1068. auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
  1069. if (debug) {
  1070. layout = Settings::ControllerType::ProController;
  1071. }
  1072. switch (layout) {
  1073. case Settings::ControllerType::ProController:
  1074. case Settings::ControllerType::DualJoyconDetached:
  1075. case Settings::ControllerType::Handheld:
  1076. case Settings::ControllerType::LeftJoycon:
  1077. case Settings::ControllerType::RightJoycon:
  1078. ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus"));
  1079. ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL"));
  1080. ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR"));
  1081. ui->buttonShoulderButtonsRGroup->setTitle(tr("R"));
  1082. ui->LStick->setTitle(tr("Left Stick"));
  1083. ui->RStick->setTitle(tr("Right Stick"));
  1084. break;
  1085. case Settings::ControllerType::GameCube:
  1086. ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause"));
  1087. ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L"));
  1088. ui->buttonShoulderButtonsZRGroup->setTitle(tr("R"));
  1089. ui->buttonShoulderButtonsRGroup->setTitle(tr("Z"));
  1090. ui->LStick->setTitle(tr("Control Stick"));
  1091. ui->RStick->setTitle(tr("C-Stick"));
  1092. break;
  1093. }
  1094. }
  1095. void ConfigureInputPlayer::UpdateMappingWithDefaults() {
  1096. if (ui->comboDevices->currentIndex() == 0) {
  1097. return;
  1098. }
  1099. if (ui->comboDevices->currentIndex() == 1) {
  1100. // Reset keyboard bindings
  1101. for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
  1102. buttons_param[button_id] = Common::ParamPackage{
  1103. InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
  1104. }
  1105. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  1106. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  1107. Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
  1108. Config::default_analogs[analog_id][sub_button_id])};
  1109. SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
  1110. }
  1111. analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam(
  1112. Config::default_stick_mod[analog_id]));
  1113. }
  1114. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  1115. motions_param[motion_id] = Common::ParamPackage{
  1116. InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])};
  1117. }
  1118. UpdateUI();
  1119. return;
  1120. }
  1121. // Reset controller bindings
  1122. const auto& device = input_devices[ui->comboDevices->currentIndex()];
  1123. auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
  1124. auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
  1125. auto motion_mapping = input_subsystem->GetMotionMappingForDevice(device);
  1126. for (std::size_t i = 0; i < buttons_param.size(); ++i) {
  1127. buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
  1128. }
  1129. for (std::size_t i = 0; i < analogs_param.size(); ++i) {
  1130. analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
  1131. }
  1132. for (std::size_t i = 0; i < motions_param.size(); ++i) {
  1133. motions_param[i] = motion_mapping[static_cast<Settings::NativeMotion::Values>(i)];
  1134. }
  1135. UpdateUI();
  1136. }
  1137. void ConfigureInputPlayer::HandleClick(
  1138. QPushButton* button, std::size_t button_id,
  1139. std::function<void(const Common::ParamPackage&)> new_input_setter,
  1140. InputCommon::Polling::DeviceType type) {
  1141. if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
  1142. button->setText(tr("Shake!"));
  1143. } else {
  1144. button->setText(tr("[waiting]"));
  1145. }
  1146. button->setFocus();
  1147. // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
  1148. // controller, then they don't want keyboard/mouse input
  1149. want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;
  1150. input_setter = new_input_setter;
  1151. device_pollers = input_subsystem->GetPollers(type);
  1152. for (auto& poller : device_pollers) {
  1153. poller->Start();
  1154. }
  1155. QWidget::grabMouse();
  1156. QWidget::grabKeyboard();
  1157. if (type == InputCommon::Polling::DeviceType::Button) {
  1158. input_subsystem->GetGCButtons()->BeginConfiguration();
  1159. } else {
  1160. input_subsystem->GetGCAnalogs()->BeginConfiguration();
  1161. }
  1162. if (type == InputCommon::Polling::DeviceType::Motion) {
  1163. input_subsystem->GetUDPMotions()->BeginConfiguration();
  1164. }
  1165. if (type == InputCommon::Polling::DeviceType::Button) {
  1166. input_subsystem->GetMouseButtons()->BeginConfiguration();
  1167. } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) {
  1168. input_subsystem->GetMouseAnalogs()->BeginConfiguration();
  1169. } else if (type == InputCommon::Polling::DeviceType::Motion) {
  1170. input_subsystem->GetMouseMotions()->BeginConfiguration();
  1171. } else {
  1172. input_subsystem->GetMouseTouch()->BeginConfiguration();
  1173. }
  1174. if (type == InputCommon::Polling::DeviceType::Button) {
  1175. ui->controllerFrame->BeginMappingButton(button_id);
  1176. } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) {
  1177. ui->controllerFrame->BeginMappingAnalog(button_id);
  1178. }
  1179. timeout_timer->start(2500); // Cancel after 2.5 seconds
  1180. poll_timer->start(50); // Check for new inputs every 50ms
  1181. }
  1182. void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
  1183. timeout_timer->stop();
  1184. poll_timer->stop();
  1185. for (auto& poller : device_pollers) {
  1186. poller->Stop();
  1187. }
  1188. QWidget::releaseMouse();
  1189. QWidget::releaseKeyboard();
  1190. input_subsystem->GetGCButtons()->EndConfiguration();
  1191. input_subsystem->GetGCAnalogs()->EndConfiguration();
  1192. input_subsystem->GetUDPMotions()->EndConfiguration();
  1193. input_subsystem->GetMouseButtons()->EndConfiguration();
  1194. input_subsystem->GetMouseAnalogs()->EndConfiguration();
  1195. input_subsystem->GetMouseMotions()->EndConfiguration();
  1196. input_subsystem->GetMouseTouch()->EndConfiguration();
  1197. if (!abort) {
  1198. (*input_setter)(params);
  1199. }
  1200. UpdateUI();
  1201. UpdateInputDeviceCombobox();
  1202. ui->controllerFrame->EndMapping();
  1203. input_setter = std::nullopt;
  1204. }
  1205. bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const {
  1206. if (ui->comboDevices->currentIndex() == 0) {
  1207. return true;
  1208. }
  1209. // Keyboard/Mouse
  1210. if (ui->comboDevices->currentIndex() == 1) {
  1211. return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse";
  1212. }
  1213. const auto current_input_device = input_devices[ui->comboDevices->currentIndex()];
  1214. return params.Get("engine", "") == current_input_device.Get("class", "") &&
  1215. params.Get("guid", "") == current_input_device.Get("guid", "") &&
  1216. params.Get("port", "") == current_input_device.Get("port", "");
  1217. }
  1218. void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
  1219. if (!input_setter || !event) {
  1220. return;
  1221. }
  1222. const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
  1223. input_subsystem->GetMouse()->PressButton(0, 0, button);
  1224. }
  1225. void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
  1226. if (!input_setter || !event) {
  1227. return;
  1228. }
  1229. if (event->key() != Qt::Key_Escape) {
  1230. if (want_keyboard_mouse) {
  1231. SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
  1232. false);
  1233. } else {
  1234. // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
  1235. return;
  1236. }
  1237. }
  1238. SetPollingResult({}, true);
  1239. }
  1240. void ConfigureInputPlayer::CreateProfile() {
  1241. const auto profile_name =
  1242. LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20,
  1243. LimitableInputDialog::InputLimiter::Filesystem);
  1244. if (profile_name.isEmpty()) {
  1245. return;
  1246. }
  1247. if (!InputProfiles::IsProfileNameValid(profile_name.toStdString())) {
  1248. QMessageBox::critical(this, tr("Create Input Profile"),
  1249. tr("The given profile name is not valid!"));
  1250. return;
  1251. }
  1252. ApplyConfiguration();
  1253. if (!profiles->CreateProfile(profile_name.toStdString(), player_index)) {
  1254. QMessageBox::critical(this, tr("Create Input Profile"),
  1255. tr("Failed to create the input profile \"%1\"").arg(profile_name));
  1256. UpdateInputProfiles();
  1257. emit RefreshInputProfiles(player_index);
  1258. return;
  1259. }
  1260. emit RefreshInputProfiles(player_index);
  1261. ui->comboProfiles->addItem(profile_name);
  1262. ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1);
  1263. }
  1264. void ConfigureInputPlayer::DeleteProfile() {
  1265. const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
  1266. if (profile_name.isEmpty()) {
  1267. return;
  1268. }
  1269. if (!profiles->DeleteProfile(profile_name.toStdString())) {
  1270. QMessageBox::critical(this, tr("Delete Input Profile"),
  1271. tr("Failed to delete the input profile \"%1\"").arg(profile_name));
  1272. UpdateInputProfiles();
  1273. emit RefreshInputProfiles(player_index);
  1274. return;
  1275. }
  1276. emit RefreshInputProfiles(player_index);
  1277. ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex());
  1278. ui->comboProfiles->setCurrentIndex(-1);
  1279. }
  1280. void ConfigureInputPlayer::LoadProfile() {
  1281. const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
  1282. if (profile_name.isEmpty()) {
  1283. return;
  1284. }
  1285. ApplyConfiguration();
  1286. if (!profiles->LoadProfile(profile_name.toStdString(), player_index)) {
  1287. QMessageBox::critical(this, tr("Load Input Profile"),
  1288. tr("Failed to load the input profile \"%1\"").arg(profile_name));
  1289. UpdateInputProfiles();
  1290. emit RefreshInputProfiles(player_index);
  1291. return;
  1292. }
  1293. LoadConfiguration();
  1294. }
  1295. void ConfigureInputPlayer::SaveProfile() {
  1296. const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
  1297. if (profile_name.isEmpty()) {
  1298. return;
  1299. }
  1300. ApplyConfiguration();
  1301. if (!profiles->SaveProfile(profile_name.toStdString(), player_index)) {
  1302. QMessageBox::critical(this, tr("Save Input Profile"),
  1303. tr("Failed to save the input profile \"%1\"").arg(profile_name));
  1304. UpdateInputProfiles();
  1305. emit RefreshInputProfiles(player_index);
  1306. return;
  1307. }
  1308. }
  1309. void ConfigureInputPlayer::UpdateInputProfiles() {
  1310. ui->comboProfiles->clear();
  1311. for (const auto& profile_name : profiles->GetInputProfileNames()) {
  1312. ui->comboProfiles->addItem(QString::fromStdString(profile_name));
  1313. }
  1314. ui->comboProfiles->setCurrentIndex(-1);
  1315. }