configure_input_player.cpp 66 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633
  1. // SPDX-FileCopyrightText: 2016 Citra Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <algorithm>
  4. #include <memory>
  5. #include <utility>
  6. #include <QGridLayout>
  7. #include <QInputDialog>
  8. #include <QMenu>
  9. #include <QMessageBox>
  10. #include <QMouseEvent>
  11. #include <QTimer>
  12. #include "common/assert.h"
  13. #include "common/param_package.h"
  14. #include "core/hid/emulated_controller.h"
  15. #include "core/hid/hid_core.h"
  16. #include "core/hid/hid_types.h"
  17. #include "input_common/drivers/keyboard.h"
  18. #include "input_common/drivers/mouse.h"
  19. #include "input_common/main.h"
  20. #include "ui_configure_input_player.h"
  21. #include "yuzu/bootmanager.h"
  22. #include "yuzu/configuration/config.h"
  23. #include "yuzu/configuration/configure_input_player.h"
  24. #include "yuzu/configuration/configure_input_player_widget.h"
  25. #include "yuzu/configuration/input_profiles.h"
  26. #include "yuzu/util/limitable_input_dialog.h"
  27. const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
  28. ConfigureInputPlayer::analog_sub_buttons{{
  29. "up",
  30. "down",
  31. "left",
  32. "right",
  33. }};
  34. namespace {
  35. QString GetKeyName(int key_code) {
  36. switch (key_code) {
  37. case Qt::Key_Shift:
  38. return QObject::tr("Shift");
  39. case Qt::Key_Control:
  40. return QObject::tr("Ctrl");
  41. case Qt::Key_Alt:
  42. return QObject::tr("Alt");
  43. case Qt::Key_Meta:
  44. return {};
  45. default:
  46. return QKeySequence(key_code).toString();
  47. }
  48. }
  49. QString GetButtonName(Common::Input::ButtonNames button_name) {
  50. switch (button_name) {
  51. case Common::Input::ButtonNames::ButtonLeft:
  52. return QObject::tr("Left");
  53. case Common::Input::ButtonNames::ButtonRight:
  54. return QObject::tr("Right");
  55. case Common::Input::ButtonNames::ButtonDown:
  56. return QObject::tr("Down");
  57. case Common::Input::ButtonNames::ButtonUp:
  58. return QObject::tr("Up");
  59. case Common::Input::ButtonNames::TriggerZ:
  60. return QObject::tr("Z");
  61. case Common::Input::ButtonNames::TriggerR:
  62. return QObject::tr("R");
  63. case Common::Input::ButtonNames::TriggerL:
  64. return QObject::tr("L");
  65. case Common::Input::ButtonNames::TriggerZR:
  66. return QObject::tr("ZR");
  67. case Common::Input::ButtonNames::TriggerZL:
  68. return QObject::tr("ZL");
  69. case Common::Input::ButtonNames::TriggerSR:
  70. return QObject::tr("SR");
  71. case Common::Input::ButtonNames::TriggerSL:
  72. return QObject::tr("SL");
  73. case Common::Input::ButtonNames::ButtonStickL:
  74. return QObject::tr("Stick L");
  75. case Common::Input::ButtonNames::ButtonStickR:
  76. return QObject::tr("Stick R");
  77. case Common::Input::ButtonNames::ButtonA:
  78. return QObject::tr("A");
  79. case Common::Input::ButtonNames::ButtonB:
  80. return QObject::tr("B");
  81. case Common::Input::ButtonNames::ButtonX:
  82. return QObject::tr("X");
  83. case Common::Input::ButtonNames::ButtonY:
  84. return QObject::tr("Y");
  85. case Common::Input::ButtonNames::ButtonStart:
  86. return QObject::tr("Start");
  87. case Common::Input::ButtonNames::ButtonPlus:
  88. return QObject::tr("Plus");
  89. case Common::Input::ButtonNames::ButtonMinus:
  90. return QObject::tr("Minus");
  91. case Common::Input::ButtonNames::ButtonHome:
  92. return QObject::tr("Home");
  93. case Common::Input::ButtonNames::ButtonCapture:
  94. return QObject::tr("Capture");
  95. case Common::Input::ButtonNames::L1:
  96. return QObject::tr("L1");
  97. case Common::Input::ButtonNames::L2:
  98. return QObject::tr("L2");
  99. case Common::Input::ButtonNames::L3:
  100. return QObject::tr("L3");
  101. case Common::Input::ButtonNames::R1:
  102. return QObject::tr("R1");
  103. case Common::Input::ButtonNames::R2:
  104. return QObject::tr("R2");
  105. case Common::Input::ButtonNames::R3:
  106. return QObject::tr("R3");
  107. case Common::Input::ButtonNames::Circle:
  108. return QObject::tr("Circle");
  109. case Common::Input::ButtonNames::Cross:
  110. return QObject::tr("Cross");
  111. case Common::Input::ButtonNames::Square:
  112. return QObject::tr("Square");
  113. case Common::Input::ButtonNames::Triangle:
  114. return QObject::tr("Triangle");
  115. case Common::Input::ButtonNames::Share:
  116. return QObject::tr("Share");
  117. case Common::Input::ButtonNames::Options:
  118. return QObject::tr("Options");
  119. case Common::Input::ButtonNames::Home:
  120. return QObject::tr("Home");
  121. case Common::Input::ButtonNames::Touch:
  122. return QObject::tr("Touch");
  123. case Common::Input::ButtonNames::ButtonMouseWheel:
  124. return QObject::tr("Wheel", "Indicates the mouse wheel");
  125. case Common::Input::ButtonNames::ButtonBackward:
  126. return QObject::tr("Backward");
  127. case Common::Input::ButtonNames::ButtonForward:
  128. return QObject::tr("Forward");
  129. case Common::Input::ButtonNames::ButtonTask:
  130. return QObject::tr("Task");
  131. case Common::Input::ButtonNames::ButtonExtra:
  132. return QObject::tr("Extra");
  133. default:
  134. return QObject::tr("[undefined]");
  135. }
  136. }
  137. QString GetDirectionName(const std::string& direction) {
  138. if (direction == "left") {
  139. return QObject::tr("Left");
  140. }
  141. if (direction == "right") {
  142. return QObject::tr("Right");
  143. }
  144. if (direction == "up") {
  145. return QObject::tr("Up");
  146. }
  147. if (direction == "down") {
  148. return QObject::tr("Down");
  149. }
  150. UNIMPLEMENTED_MSG("Unimplemented direction name={}", direction);
  151. return QString::fromStdString(direction);
  152. }
  153. void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
  154. const std::string& button_name) {
  155. // The poller returned a complete axis, so set all the buttons
  156. if (input_param.Has("axis_x") && input_param.Has("axis_y")) {
  157. analog_param = input_param;
  158. return;
  159. }
  160. // Check if the current configuration has either no engine or an axis binding.
  161. // Clears out the old binding and adds one with analog_from_button.
  162. if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) {
  163. analog_param = {
  164. {"engine", "analog_from_button"},
  165. };
  166. }
  167. analog_param.Set(button_name, input_param.Serialize());
  168. }
  169. } // namespace
  170. QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
  171. if (!param.Has("engine")) {
  172. return QObject::tr("[not set]");
  173. }
  174. const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
  175. const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : "");
  176. const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : "");
  177. const QString turbo = QString::fromStdString(param.Get("turbo", false) ? "$" : "");
  178. const auto common_button_name = input_subsystem->GetButtonName(param);
  179. // Retrieve the names from Qt
  180. if (param.Get("engine", "") == "keyboard") {
  181. const QString button_str = GetKeyName(param.Get("code", 0));
  182. return QObject::tr("%1%2%3%4").arg(turbo, toggle, inverted, button_str);
  183. }
  184. if (common_button_name == Common::Input::ButtonNames::Invalid) {
  185. return QObject::tr("[invalid]");
  186. }
  187. if (common_button_name == Common::Input::ButtonNames::Engine) {
  188. return QString::fromStdString(param.Get("engine", ""));
  189. }
  190. if (common_button_name == Common::Input::ButtonNames::Value) {
  191. if (param.Has("hat")) {
  192. const QString hat = GetDirectionName(param.Get("direction", ""));
  193. return QObject::tr("%1%2%3Hat %4").arg(turbo, toggle, inverted, hat);
  194. }
  195. if (param.Has("axis")) {
  196. const QString axis = QString::fromStdString(param.Get("axis", ""));
  197. return QObject::tr("%1%2%3Axis %4").arg(toggle, inverted, invert, axis);
  198. }
  199. if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) {
  200. const QString axis_x = QString::fromStdString(param.Get("axis_x", ""));
  201. const QString axis_y = QString::fromStdString(param.Get("axis_y", ""));
  202. const QString axis_z = QString::fromStdString(param.Get("axis_z", ""));
  203. return QObject::tr("%1%2Axis %3,%4,%5").arg(toggle, inverted, axis_x, axis_y, axis_z);
  204. }
  205. if (param.Has("motion")) {
  206. const QString motion = QString::fromStdString(param.Get("motion", ""));
  207. return QObject::tr("%1%2Motion %3").arg(toggle, inverted, motion);
  208. }
  209. if (param.Has("button")) {
  210. const QString button = QString::fromStdString(param.Get("button", ""));
  211. return QObject::tr("%1%2%3Button %4").arg(turbo, toggle, inverted, button);
  212. }
  213. }
  214. QString button_name = GetButtonName(common_button_name);
  215. if (param.Has("hat")) {
  216. return QObject::tr("%1%2%3Hat %4").arg(turbo, toggle, inverted, button_name);
  217. }
  218. if (param.Has("axis")) {
  219. return QObject::tr("%1%2%3Axis %4").arg(toggle, inverted, invert, button_name);
  220. }
  221. if (param.Has("motion")) {
  222. return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
  223. }
  224. if (param.Has("button")) {
  225. return QObject::tr("%1%2%3Button %4").arg(turbo, toggle, inverted, button_name);
  226. }
  227. return QObject::tr("[unknown]");
  228. }
  229. QString ConfigureInputPlayer::AnalogToText(const Common::ParamPackage& param,
  230. const std::string& dir) {
  231. if (!param.Has("engine")) {
  232. return QObject::tr("[not set]");
  233. }
  234. if (param.Get("engine", "") == "analog_from_button") {
  235. return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
  236. }
  237. if (!param.Has("axis_x") || !param.Has("axis_y")) {
  238. return QObject::tr("[unknown]");
  239. }
  240. const auto engine_str = param.Get("engine", "");
  241. const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
  242. const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
  243. const bool invert_x = param.Get("invert_x", "+") == "-";
  244. const bool invert_y = param.Get("invert_y", "+") == "-";
  245. if (dir == "modifier") {
  246. return QObject::tr("[unused]");
  247. }
  248. if (dir == "left") {
  249. const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-");
  250. return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
  251. }
  252. if (dir == "right") {
  253. const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+");
  254. return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
  255. }
  256. if (dir == "up") {
  257. const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+");
  258. return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
  259. }
  260. if (dir == "down") {
  261. const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-");
  262. return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
  263. }
  264. return QObject::tr("[unknown]");
  265. }
  266. ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index_,
  267. QWidget* bottom_row_,
  268. InputCommon::InputSubsystem* input_subsystem_,
  269. InputProfiles* profiles_, Core::HID::HIDCore& hid_core_,
  270. bool is_powered_on_, bool debug_)
  271. : QWidget(parent),
  272. ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index{player_index_}, debug{debug_},
  273. is_powered_on{is_powered_on_}, input_subsystem{input_subsystem_}, profiles(profiles_),
  274. timeout_timer(std::make_unique<QTimer>()),
  275. poll_timer(std::make_unique<QTimer>()), bottom_row{bottom_row_}, hid_core{hid_core_} {
  276. if (player_index == 0) {
  277. auto* emulated_controller_p1 =
  278. hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
  279. auto* emulated_controller_handheld =
  280. hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
  281. emulated_controller_p1->SaveCurrentConfig();
  282. emulated_controller_p1->EnableConfiguration();
  283. emulated_controller_handheld->SaveCurrentConfig();
  284. emulated_controller_handheld->EnableConfiguration();
  285. if (emulated_controller_handheld->IsConnected(true)) {
  286. emulated_controller_p1->Disconnect();
  287. emulated_controller = emulated_controller_handheld;
  288. } else {
  289. emulated_controller = emulated_controller_p1;
  290. }
  291. } else {
  292. emulated_controller = hid_core.GetEmulatedControllerByIndex(player_index);
  293. emulated_controller->SaveCurrentConfig();
  294. emulated_controller->EnableConfiguration();
  295. }
  296. ui->setupUi(this);
  297. setFocusPolicy(Qt::ClickFocus);
  298. button_map = {
  299. ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY,
  300. ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR,
  301. ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus,
  302. ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown,
  303. ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot,
  304. };
  305. analog_map_buttons = {{
  306. {
  307. ui->buttonLStickUp,
  308. ui->buttonLStickDown,
  309. ui->buttonLStickLeft,
  310. ui->buttonLStickRight,
  311. },
  312. {
  313. ui->buttonRStickUp,
  314. ui->buttonRStickDown,
  315. ui->buttonRStickLeft,
  316. ui->buttonRStickRight,
  317. },
  318. }};
  319. motion_map = {
  320. ui->buttonMotionLeft,
  321. ui->buttonMotionRight,
  322. };
  323. analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone};
  324. analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone};
  325. analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup};
  326. analog_map_modifier_button = {ui->buttonLStickMod, ui->buttonRStickMod};
  327. analog_map_modifier_label = {ui->labelLStickModifierRange, ui->labelRStickModifierRange};
  328. analog_map_modifier_slider = {ui->sliderLStickModifierRange, ui->sliderRStickModifierRange};
  329. analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup};
  330. analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange};
  331. ui->controllerFrame->SetController(emulated_controller);
  332. for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
  333. auto* const button = button_map[button_id];
  334. if (button == nullptr) {
  335. continue;
  336. }
  337. connect(button, &QPushButton::clicked, [=, this] {
  338. HandleClick(
  339. button, button_id,
  340. [=, this](const Common::ParamPackage& params) {
  341. emulated_controller->SetButtonParam(button_id, params);
  342. },
  343. InputCommon::Polling::InputType::Button);
  344. });
  345. button->setContextMenuPolicy(Qt::CustomContextMenu);
  346. connect(button, &QPushButton::customContextMenuRequested,
  347. [=, this](const QPoint& menu_location) {
  348. QMenu context_menu;
  349. Common::ParamPackage param = emulated_controller->GetButtonParam(button_id);
  350. context_menu.addAction(tr("Clear"), [&] {
  351. emulated_controller->SetButtonParam(button_id, {});
  352. button_map[button_id]->setText(tr("[not set]"));
  353. });
  354. if (param.Has("code") || param.Has("button") || param.Has("hat")) {
  355. context_menu.addAction(tr("Invert button"), [&] {
  356. const bool invert_value = !param.Get("inverted", false);
  357. param.Set("inverted", invert_value);
  358. button_map[button_id]->setText(ButtonToText(param));
  359. emulated_controller->SetButtonParam(button_id, param);
  360. });
  361. context_menu.addAction(tr("Toggle button"), [&] {
  362. const bool toggle_value = !param.Get("toggle", false);
  363. param.Set("toggle", toggle_value);
  364. button_map[button_id]->setText(ButtonToText(param));
  365. emulated_controller->SetButtonParam(button_id, param);
  366. });
  367. context_menu.addAction(tr("Turbo button"), [&] {
  368. const bool turbo_value = !param.Get("turbo", false);
  369. param.Set("turbo", turbo_value);
  370. button_map[button_id]->setText(ButtonToText(param));
  371. emulated_controller->SetButtonParam(button_id, param);
  372. });
  373. }
  374. if (param.Has("axis")) {
  375. context_menu.addAction(tr("Invert axis"), [&] {
  376. const bool toggle_value = !(param.Get("invert", "+") == "-");
  377. param.Set("invert", toggle_value ? "-" : "+");
  378. button_map[button_id]->setText(ButtonToText(param));
  379. emulated_controller->SetButtonParam(button_id, param);
  380. });
  381. context_menu.addAction(tr("Invert button"), [&] {
  382. const bool invert_value = !param.Get("inverted", false);
  383. param.Set("inverted", invert_value);
  384. button_map[button_id]->setText(ButtonToText(param));
  385. emulated_controller->SetButtonParam(button_id, param);
  386. });
  387. context_menu.addAction(tr("Set threshold"), [&] {
  388. const int button_threshold =
  389. static_cast<int>(param.Get("threshold", 0.5f) * 100.0f);
  390. const int new_threshold = QInputDialog::getInt(
  391. this, tr("Set threshold"), tr("Choose a value between 0% and 100%"),
  392. button_threshold, 0, 100);
  393. param.Set("threshold", new_threshold / 100.0f);
  394. if (button_id == Settings::NativeButton::ZL) {
  395. ui->sliderZLThreshold->setValue(new_threshold);
  396. }
  397. if (button_id == Settings::NativeButton::ZR) {
  398. ui->sliderZRThreshold->setValue(new_threshold);
  399. }
  400. emulated_controller->SetButtonParam(button_id, param);
  401. });
  402. context_menu.addAction(tr("Toggle axis"), [&] {
  403. const bool toggle_value = !param.Get("toggle", false);
  404. param.Set("toggle", toggle_value);
  405. button_map[button_id]->setText(ButtonToText(param));
  406. emulated_controller->SetButtonParam(button_id, param);
  407. });
  408. }
  409. context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
  410. });
  411. }
  412. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  413. auto* const button = motion_map[motion_id];
  414. if (button == nullptr) {
  415. continue;
  416. }
  417. connect(button, &QPushButton::clicked, [=, this] {
  418. HandleClick(
  419. button, motion_id,
  420. [=, this](const Common::ParamPackage& params) {
  421. emulated_controller->SetMotionParam(motion_id, params);
  422. },
  423. InputCommon::Polling::InputType::Motion);
  424. });
  425. button->setContextMenuPolicy(Qt::CustomContextMenu);
  426. connect(button, &QPushButton::customContextMenuRequested,
  427. [=, this](const QPoint& menu_location) {
  428. QMenu context_menu;
  429. Common::ParamPackage param = emulated_controller->GetMotionParam(motion_id);
  430. context_menu.addAction(tr("Clear"), [&] {
  431. emulated_controller->SetMotionParam(motion_id, {});
  432. motion_map[motion_id]->setText(tr("[not set]"));
  433. });
  434. if (param.Has("motion")) {
  435. context_menu.addAction(tr("Set gyro threshold"), [&] {
  436. const int gyro_threshold =
  437. static_cast<int>(param.Get("threshold", 0.007f) * 1000.0f);
  438. const int new_threshold = QInputDialog::getInt(
  439. this, tr("Set threshold"), tr("Choose a value between 0% and 100%"),
  440. gyro_threshold, 0, 100);
  441. param.Set("threshold", new_threshold / 1000.0f);
  442. emulated_controller->SetMotionParam(motion_id, param);
  443. });
  444. context_menu.addAction(tr("Calibrate sensor"), [&] {
  445. emulated_controller->StartMotionCalibration();
  446. });
  447. }
  448. context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location));
  449. });
  450. }
  451. connect(ui->sliderZLThreshold, &QSlider::valueChanged, [=, this] {
  452. Common::ParamPackage param =
  453. emulated_controller->GetButtonParam(Settings::NativeButton::ZL);
  454. if (param.Has("threshold")) {
  455. const auto slider_value = ui->sliderZLThreshold->value();
  456. param.Set("threshold", slider_value / 100.0f);
  457. emulated_controller->SetButtonParam(Settings::NativeButton::ZL, param);
  458. }
  459. });
  460. connect(ui->sliderZRThreshold, &QSlider::valueChanged, [=, this] {
  461. Common::ParamPackage param =
  462. emulated_controller->GetButtonParam(Settings::NativeButton::ZR);
  463. if (param.Has("threshold")) {
  464. const auto slider_value = ui->sliderZRThreshold->value();
  465. param.Set("threshold", slider_value / 100.0f);
  466. emulated_controller->SetButtonParam(Settings::NativeButton::ZR, param);
  467. }
  468. });
  469. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  470. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  471. auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
  472. if (analog_button == nullptr) {
  473. continue;
  474. }
  475. connect(analog_button, &QPushButton::clicked, [=, this] {
  476. if (!map_analog_stick_accepted) {
  477. map_analog_stick_accepted =
  478. QMessageBox::information(
  479. this, tr("Map Analog Stick"),
  480. tr("After pressing OK, first move your joystick horizontally, and then "
  481. "vertically.\nTo invert the axes, first move your joystick "
  482. "vertically, and then horizontally."),
  483. QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok;
  484. if (!map_analog_stick_accepted) {
  485. return;
  486. }
  487. }
  488. HandleClick(
  489. analog_map_buttons[analog_id][sub_button_id], analog_id,
  490. [=, this](const Common::ParamPackage& params) {
  491. Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
  492. SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
  493. // Correct axis direction for inverted sticks
  494. if (input_subsystem->IsStickInverted(param)) {
  495. switch (analog_id) {
  496. case Settings::NativeAnalog::LStick: {
  497. const bool invert_value = param.Get("invert_x", "+") == "-";
  498. const std::string invert_str = invert_value ? "+" : "-";
  499. param.Set("invert_x", invert_str);
  500. break;
  501. }
  502. case Settings::NativeAnalog::RStick: {
  503. const bool invert_value = param.Get("invert_y", "+") == "-";
  504. const std::string invert_str = invert_value ? "+" : "-";
  505. param.Set("invert_y", invert_str);
  506. break;
  507. }
  508. default:
  509. break;
  510. }
  511. }
  512. emulated_controller->SetStickParam(analog_id, param);
  513. },
  514. InputCommon::Polling::InputType::Stick);
  515. });
  516. analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
  517. connect(analog_button, &QPushButton::customContextMenuRequested,
  518. [=, this](const QPoint& menu_location) {
  519. QMenu context_menu;
  520. Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
  521. context_menu.addAction(tr("Clear"), [&] {
  522. if (param.Get("engine", "") != "analog_from_button") {
  523. emulated_controller->SetStickParam(analog_id, {});
  524. for (auto button : analog_map_buttons[analog_id]) {
  525. button->setText(tr("[not set]"));
  526. }
  527. return;
  528. }
  529. switch (sub_button_id) {
  530. case 0:
  531. param.Erase("up");
  532. break;
  533. case 1:
  534. param.Erase("down");
  535. break;
  536. case 2:
  537. param.Erase("left");
  538. break;
  539. case 3:
  540. param.Erase("right");
  541. break;
  542. }
  543. emulated_controller->SetStickParam(analog_id, param);
  544. analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
  545. });
  546. context_menu.addAction(tr("Center axis"), [&] {
  547. const auto stick_value =
  548. emulated_controller->GetSticksValues()[analog_id];
  549. const float offset_x = stick_value.x.properties.offset;
  550. const float offset_y = stick_value.y.properties.offset;
  551. float raw_value_x = stick_value.x.raw_value;
  552. float raw_value_y = stick_value.y.raw_value;
  553. // See Core::HID::SanitizeStick() to obtain the original raw axis value
  554. if (std::abs(offset_x) < 0.5f) {
  555. if (raw_value_x > 0) {
  556. raw_value_x *= 1 + offset_x;
  557. } else {
  558. raw_value_x *= 1 - offset_x;
  559. }
  560. }
  561. if (std::abs(offset_x) < 0.5f) {
  562. if (raw_value_y > 0) {
  563. raw_value_y *= 1 + offset_y;
  564. } else {
  565. raw_value_y *= 1 - offset_y;
  566. }
  567. }
  568. param.Set("offset_x", -raw_value_x + offset_x);
  569. param.Set("offset_y", -raw_value_y + offset_y);
  570. emulated_controller->SetStickParam(analog_id, param);
  571. });
  572. context_menu.addAction(tr("Invert axis"), [&] {
  573. if (sub_button_id == 2 || sub_button_id == 3) {
  574. const bool invert_value = param.Get("invert_x", "+") == "-";
  575. const std::string invert_str = invert_value ? "+" : "-";
  576. param.Set("invert_x", invert_str);
  577. emulated_controller->SetStickParam(analog_id, param);
  578. }
  579. if (sub_button_id == 0 || sub_button_id == 1) {
  580. const bool invert_value = param.Get("invert_y", "+") == "-";
  581. const std::string invert_str = invert_value ? "+" : "-";
  582. param.Set("invert_y", invert_str);
  583. emulated_controller->SetStickParam(analog_id, param);
  584. }
  585. for (int analog_sub_button_id = 0;
  586. analog_sub_button_id < ANALOG_SUB_BUTTONS_NUM;
  587. ++analog_sub_button_id) {
  588. analog_map_buttons[analog_id][analog_sub_button_id]->setText(
  589. AnalogToText(param, analog_sub_buttons[analog_sub_button_id]));
  590. }
  591. });
  592. context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
  593. menu_location));
  594. });
  595. }
  596. // Handle clicks for the modifier buttons as well.
  597. connect(analog_map_modifier_button[analog_id], &QPushButton::clicked, [=, this] {
  598. HandleClick(
  599. analog_map_modifier_button[analog_id], analog_id,
  600. [=, this](const Common::ParamPackage& params) {
  601. Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
  602. param.Set("modifier", params.Serialize());
  603. emulated_controller->SetStickParam(analog_id, param);
  604. },
  605. InputCommon::Polling::InputType::Button);
  606. });
  607. analog_map_modifier_button[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu);
  608. connect(
  609. analog_map_modifier_button[analog_id], &QPushButton::customContextMenuRequested,
  610. [=, this](const QPoint& menu_location) {
  611. QMenu context_menu;
  612. Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
  613. context_menu.addAction(tr("Clear"), [&] {
  614. param.Set("modifier", "");
  615. analog_map_modifier_button[analog_id]->setText(tr("[not set]"));
  616. emulated_controller->SetStickParam(analog_id, param);
  617. });
  618. context_menu.addAction(tr("Toggle button"), [&] {
  619. Common::ParamPackage modifier_param =
  620. Common::ParamPackage{param.Get("modifier", "")};
  621. const bool toggle_value = !modifier_param.Get("toggle", false);
  622. modifier_param.Set("toggle", toggle_value);
  623. param.Set("modifier", modifier_param.Serialize());
  624. analog_map_modifier_button[analog_id]->setText(ButtonToText(modifier_param));
  625. emulated_controller->SetStickParam(analog_id, param);
  626. });
  627. context_menu.addAction(tr("Invert button"), [&] {
  628. Common::ParamPackage modifier_param =
  629. Common::ParamPackage{param.Get("modifier", "")};
  630. const bool invert_value = !modifier_param.Get("inverted", false);
  631. modifier_param.Set("inverted", invert_value);
  632. param.Set("modifier", modifier_param.Serialize());
  633. analog_map_modifier_button[analog_id]->setText(ButtonToText(modifier_param));
  634. emulated_controller->SetStickParam(analog_id, param);
  635. });
  636. context_menu.exec(
  637. analog_map_modifier_button[analog_id]->mapToGlobal(menu_location));
  638. });
  639. connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged),
  640. [=, this] {
  641. Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
  642. const auto spinbox_value = analog_map_range_spinbox[analog_id]->value();
  643. param.Set("range", spinbox_value / 100.0f);
  644. emulated_controller->SetStickParam(analog_id, param);
  645. });
  646. connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] {
  647. Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
  648. const auto slider_value = analog_map_deadzone_slider[analog_id]->value();
  649. analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value));
  650. param.Set("deadzone", slider_value / 100.0f);
  651. emulated_controller->SetStickParam(analog_id, param);
  652. });
  653. connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] {
  654. Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
  655. const auto slider_value = analog_map_modifier_slider[analog_id]->value();
  656. analog_map_modifier_label[analog_id]->setText(
  657. tr("Modifier Range: %1%").arg(slider_value));
  658. param.Set("modifier_scale", slider_value / 100.0f);
  659. emulated_controller->SetStickParam(analog_id, param);
  660. });
  661. }
  662. // Player Connected checkbox
  663. connect(ui->groupConnectedController, &QGroupBox::toggled,
  664. [this](bool checked) { emit Connected(checked); });
  665. if (player_index == 0) {
  666. connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged),
  667. [this](int index) {
  668. emit HandheldStateChanged(GetControllerTypeFromIndex(index) ==
  669. Core::HID::NpadStyleIndex::Handheld);
  670. });
  671. }
  672. if (debug || player_index == 9) {
  673. ui->groupConnectedController->setCheckable(false);
  674. }
  675. // The Debug Controller can only choose the Pro Controller.
  676. if (debug) {
  677. ui->buttonScreenshot->setEnabled(false);
  678. ui->buttonHome->setEnabled(false);
  679. ui->comboControllerType->addItem(tr("Pro Controller"));
  680. } else {
  681. SetConnectableControllers();
  682. }
  683. UpdateControllerAvailableButtons();
  684. UpdateControllerEnabledButtons();
  685. UpdateControllerButtonNames();
  686. UpdateMotionButtons();
  687. connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
  688. UpdateControllerAvailableButtons();
  689. UpdateControllerEnabledButtons();
  690. UpdateControllerButtonNames();
  691. UpdateMotionButtons();
  692. const Core::HID::NpadStyleIndex type =
  693. GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
  694. if (player_index == 0) {
  695. auto* emulated_controller_p1 =
  696. hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
  697. auto* emulated_controller_handheld =
  698. hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
  699. bool is_connected = emulated_controller->IsConnected(true);
  700. emulated_controller_p1->SetNpadStyleIndex(type);
  701. emulated_controller_handheld->SetNpadStyleIndex(type);
  702. if (is_connected) {
  703. if (type == Core::HID::NpadStyleIndex::Handheld) {
  704. emulated_controller_p1->Disconnect();
  705. emulated_controller_handheld->Connect(true);
  706. emulated_controller = emulated_controller_handheld;
  707. } else {
  708. emulated_controller_handheld->Disconnect();
  709. emulated_controller_p1->Connect(true);
  710. emulated_controller = emulated_controller_p1;
  711. }
  712. }
  713. ui->controllerFrame->SetController(emulated_controller);
  714. }
  715. emulated_controller->SetNpadStyleIndex(type);
  716. });
  717. connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
  718. &ConfigureInputPlayer::UpdateMappingWithDefaults);
  719. ui->comboDevices->installEventFilter(this);
  720. ui->comboDevices->setCurrentIndex(-1);
  721. timeout_timer->setSingleShot(true);
  722. connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
  723. connect(poll_timer.get(), &QTimer::timeout, [this] {
  724. const auto& params = input_subsystem->GetNextInput();
  725. if (params.Has("engine") && IsInputAcceptable(params)) {
  726. SetPollingResult(params, false);
  727. return;
  728. }
  729. });
  730. UpdateInputProfiles();
  731. connect(ui->buttonProfilesNew, &QPushButton::clicked, this,
  732. &ConfigureInputPlayer::CreateProfile);
  733. connect(ui->buttonProfilesDelete, &QPushButton::clicked, this,
  734. &ConfigureInputPlayer::DeleteProfile);
  735. connect(ui->comboProfiles, qOverload<int>(&QComboBox::activated), this,
  736. &ConfigureInputPlayer::LoadProfile);
  737. connect(ui->buttonProfilesSave, &QPushButton::clicked, this,
  738. &ConfigureInputPlayer::SaveProfile);
  739. LoadConfiguration();
  740. }
  741. ConfigureInputPlayer::~ConfigureInputPlayer() {
  742. if (player_index == 0) {
  743. auto* emulated_controller_p1 =
  744. hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
  745. auto* emulated_controller_handheld =
  746. hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
  747. emulated_controller_p1->DisableConfiguration();
  748. emulated_controller_handheld->DisableConfiguration();
  749. } else {
  750. emulated_controller->DisableConfiguration();
  751. }
  752. }
  753. void ConfigureInputPlayer::ApplyConfiguration() {
  754. if (player_index == 0) {
  755. auto* emulated_controller_p1 =
  756. hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
  757. auto* emulated_controller_handheld =
  758. hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
  759. emulated_controller_p1->DisableConfiguration();
  760. emulated_controller_p1->SaveCurrentConfig();
  761. emulated_controller_p1->EnableConfiguration();
  762. emulated_controller_handheld->DisableConfiguration();
  763. emulated_controller_handheld->SaveCurrentConfig();
  764. emulated_controller_handheld->EnableConfiguration();
  765. return;
  766. }
  767. emulated_controller->DisableConfiguration();
  768. emulated_controller->SaveCurrentConfig();
  769. emulated_controller->EnableConfiguration();
  770. }
  771. void ConfigureInputPlayer::showEvent(QShowEvent* event) {
  772. if (bottom_row == nullptr) {
  773. return;
  774. }
  775. QWidget::showEvent(event);
  776. ui->main->addWidget(bottom_row);
  777. }
  778. void ConfigureInputPlayer::changeEvent(QEvent* event) {
  779. if (event->type() == QEvent::LanguageChange) {
  780. RetranslateUI();
  781. }
  782. QWidget::changeEvent(event);
  783. }
  784. void ConfigureInputPlayer::RetranslateUI() {
  785. ui->retranslateUi(this);
  786. UpdateUI();
  787. }
  788. void ConfigureInputPlayer::LoadConfiguration() {
  789. emulated_controller->ReloadFromSettings();
  790. UpdateUI();
  791. UpdateInputDeviceCombobox();
  792. if (debug) {
  793. return;
  794. }
  795. const int comboBoxIndex =
  796. GetIndexFromControllerType(emulated_controller->GetNpadStyleIndex(true));
  797. ui->comboControllerType->setCurrentIndex(comboBoxIndex);
  798. ui->groupConnectedController->setChecked(emulated_controller->IsConnected(true));
  799. }
  800. void ConfigureInputPlayer::ConnectPlayer(bool connected) {
  801. ui->groupConnectedController->setChecked(connected);
  802. if (connected) {
  803. emulated_controller->Connect(true);
  804. } else {
  805. emulated_controller->Disconnect();
  806. }
  807. }
  808. void ConfigureInputPlayer::UpdateInputDeviceCombobox() {
  809. // Skip input device persistence if "Input Devices" is set to "Any".
  810. if (ui->comboDevices->currentIndex() == 0) {
  811. UpdateInputDevices();
  812. return;
  813. }
  814. const auto devices = emulated_controller->GetMappedDevices();
  815. UpdateInputDevices();
  816. if (devices.empty()) {
  817. return;
  818. }
  819. if (devices.size() > 2) {
  820. ui->comboDevices->setCurrentIndex(0);
  821. return;
  822. }
  823. const auto first_engine = devices[0].Get("engine", "");
  824. const auto first_guid = devices[0].Get("guid", "");
  825. const auto first_port = devices[0].Get("port", 0);
  826. const auto first_pad = devices[0].Get("pad", 0);
  827. if (devices.size() == 1) {
  828. const auto devices_it = std::find_if(
  829. input_devices.begin(), input_devices.end(),
  830. [first_engine, first_guid, first_port, first_pad](const Common::ParamPackage& param) {
  831. return param.Get("engine", "") == first_engine &&
  832. param.Get("guid", "") == first_guid && param.Get("port", 0) == first_port &&
  833. param.Get("pad", 0) == first_pad;
  834. });
  835. const int device_index =
  836. devices_it != input_devices.end()
  837. ? static_cast<int>(std::distance(input_devices.begin(), devices_it))
  838. : 0;
  839. ui->comboDevices->setCurrentIndex(device_index);
  840. return;
  841. }
  842. const auto second_engine = devices[1].Get("engine", "");
  843. const auto second_guid = devices[1].Get("guid", "");
  844. const auto second_port = devices[1].Get("port", 0);
  845. const bool is_keyboard_mouse = (first_engine == "keyboard" || first_engine == "mouse") &&
  846. (second_engine == "keyboard" || second_engine == "mouse");
  847. if (is_keyboard_mouse) {
  848. ui->comboDevices->setCurrentIndex(2);
  849. return;
  850. }
  851. const bool is_engine_equal = first_engine == second_engine;
  852. const bool is_port_equal = first_port == second_port;
  853. if (is_engine_equal && is_port_equal) {
  854. const auto devices_it = std::find_if(
  855. input_devices.begin(), input_devices.end(),
  856. [first_engine, first_guid, second_guid, first_port](const Common::ParamPackage& param) {
  857. const bool is_guid_valid =
  858. (param.Get("guid", "") == first_guid &&
  859. param.Get("guid2", "") == second_guid) ||
  860. (param.Get("guid", "") == second_guid && param.Get("guid2", "") == first_guid);
  861. return param.Get("engine", "") == first_engine && is_guid_valid &&
  862. param.Get("port", 0) == first_port;
  863. });
  864. const int device_index =
  865. devices_it != input_devices.end()
  866. ? static_cast<int>(std::distance(input_devices.begin(), devices_it))
  867. : 0;
  868. ui->comboDevices->setCurrentIndex(device_index);
  869. } else {
  870. ui->comboDevices->setCurrentIndex(0);
  871. }
  872. }
  873. void ConfigureInputPlayer::RestoreDefaults() {
  874. UpdateMappingWithDefaults();
  875. }
  876. void ConfigureInputPlayer::ClearAll() {
  877. for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
  878. const auto* const button = button_map[button_id];
  879. if (button == nullptr) {
  880. continue;
  881. }
  882. emulated_controller->SetButtonParam(button_id, {});
  883. }
  884. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  885. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  886. const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
  887. if (analog_button == nullptr) {
  888. continue;
  889. }
  890. emulated_controller->SetStickParam(analog_id, {});
  891. }
  892. }
  893. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  894. const auto* const motion_button = motion_map[motion_id];
  895. if (motion_button == nullptr) {
  896. continue;
  897. }
  898. emulated_controller->SetMotionParam(motion_id, {});
  899. }
  900. UpdateUI();
  901. UpdateInputDevices();
  902. }
  903. void ConfigureInputPlayer::UpdateUI() {
  904. for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) {
  905. const Common::ParamPackage param = emulated_controller->GetButtonParam(button);
  906. button_map[button]->setText(ButtonToText(param));
  907. }
  908. const Common::ParamPackage ZL_param =
  909. emulated_controller->GetButtonParam(Settings::NativeButton::ZL);
  910. if (ZL_param.Has("threshold")) {
  911. const int button_threshold = static_cast<int>(ZL_param.Get("threshold", 0.5f) * 100.0f);
  912. ui->sliderZLThreshold->setValue(button_threshold);
  913. }
  914. const Common::ParamPackage ZR_param =
  915. emulated_controller->GetButtonParam(Settings::NativeButton::ZR);
  916. if (ZR_param.Has("threshold")) {
  917. const int button_threshold = static_cast<int>(ZR_param.Get("threshold", 0.5f) * 100.0f);
  918. ui->sliderZRThreshold->setValue(button_threshold);
  919. }
  920. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  921. const Common::ParamPackage param = emulated_controller->GetMotionParam(motion_id);
  922. motion_map[motion_id]->setText(ButtonToText(param));
  923. }
  924. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  925. const Common::ParamPackage param = emulated_controller->GetStickParam(analog_id);
  926. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  927. auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
  928. if (analog_button == nullptr) {
  929. continue;
  930. }
  931. analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id]));
  932. }
  933. analog_map_modifier_button[analog_id]->setText(
  934. ButtonToText(Common::ParamPackage{param.Get("modifier", "")}));
  935. const auto deadzone_label = analog_map_deadzone_label[analog_id];
  936. const auto deadzone_slider = analog_map_deadzone_slider[analog_id];
  937. const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id];
  938. const auto modifier_label = analog_map_modifier_label[analog_id];
  939. const auto modifier_slider = analog_map_modifier_slider[analog_id];
  940. const auto range_groupbox = analog_map_range_groupbox[analog_id];
  941. const auto range_spinbox = analog_map_range_spinbox[analog_id];
  942. int slider_value;
  943. const bool is_controller = input_subsystem->IsController(param);
  944. if (is_controller) {
  945. slider_value = static_cast<int>(param.Get("deadzone", 0.15f) * 100);
  946. deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
  947. deadzone_slider->setValue(slider_value);
  948. range_spinbox->setValue(static_cast<int>(param.Get("range", 0.95f) * 100));
  949. } else {
  950. slider_value = static_cast<int>(param.Get("modifier_scale", 0.5f) * 100);
  951. modifier_label->setText(tr("Modifier Range: %1%").arg(slider_value));
  952. modifier_slider->setValue(slider_value);
  953. }
  954. deadzone_label->setVisible(is_controller);
  955. deadzone_slider->setVisible(is_controller);
  956. modifier_groupbox->setVisible(!is_controller);
  957. modifier_label->setVisible(!is_controller);
  958. modifier_slider->setVisible(!is_controller);
  959. range_groupbox->setVisible(is_controller);
  960. }
  961. }
  962. void ConfigureInputPlayer::SetConnectableControllers() {
  963. const auto npad_style_set = hid_core.GetSupportedStyleTag();
  964. index_controller_type_pairs.clear();
  965. ui->comboControllerType->clear();
  966. const auto add_item = [&](Core::HID::NpadStyleIndex controller_type,
  967. const QString& controller_name) {
  968. index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), controller_type);
  969. ui->comboControllerType->addItem(controller_name);
  970. };
  971. if (npad_style_set.fullkey == 1) {
  972. add_item(Core::HID::NpadStyleIndex::ProController, tr("Pro Controller"));
  973. }
  974. if (npad_style_set.joycon_dual == 1) {
  975. add_item(Core::HID::NpadStyleIndex::JoyconDual, tr("Dual Joycons"));
  976. }
  977. if (npad_style_set.joycon_left == 1) {
  978. add_item(Core::HID::NpadStyleIndex::JoyconLeft, tr("Left Joycon"));
  979. }
  980. if (npad_style_set.joycon_right == 1) {
  981. add_item(Core::HID::NpadStyleIndex::JoyconRight, tr("Right Joycon"));
  982. }
  983. if (player_index == 0 && npad_style_set.handheld == 1) {
  984. add_item(Core::HID::NpadStyleIndex::Handheld, tr("Handheld"));
  985. }
  986. if (npad_style_set.gamecube == 1) {
  987. add_item(Core::HID::NpadStyleIndex::GameCube, tr("GameCube Controller"));
  988. }
  989. // Disable all unsupported controllers
  990. if (!Settings::values.enable_all_controllers) {
  991. return;
  992. }
  993. if (npad_style_set.palma == 1) {
  994. add_item(Core::HID::NpadStyleIndex::Pokeball, tr("Poke Ball Plus"));
  995. }
  996. if (npad_style_set.lark == 1) {
  997. add_item(Core::HID::NpadStyleIndex::NES, tr("NES Controller"));
  998. }
  999. if (npad_style_set.lucia == 1) {
  1000. add_item(Core::HID::NpadStyleIndex::SNES, tr("SNES Controller"));
  1001. }
  1002. if (npad_style_set.lagoon == 1) {
  1003. add_item(Core::HID::NpadStyleIndex::N64, tr("N64 Controller"));
  1004. }
  1005. if (npad_style_set.lager == 1) {
  1006. add_item(Core::HID::NpadStyleIndex::SegaGenesis, tr("Sega Genesis"));
  1007. }
  1008. }
  1009. Core::HID::NpadStyleIndex ConfigureInputPlayer::GetControllerTypeFromIndex(int index) const {
  1010. const auto it =
  1011. std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(),
  1012. [index](const auto& pair) { return pair.first == index; });
  1013. if (it == index_controller_type_pairs.end()) {
  1014. return Core::HID::NpadStyleIndex::ProController;
  1015. }
  1016. return it->second;
  1017. }
  1018. int ConfigureInputPlayer::GetIndexFromControllerType(Core::HID::NpadStyleIndex type) const {
  1019. const auto it =
  1020. std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(),
  1021. [type](const auto& pair) { return pair.second == type; });
  1022. if (it == index_controller_type_pairs.end()) {
  1023. return -1;
  1024. }
  1025. return it->first;
  1026. }
  1027. void ConfigureInputPlayer::UpdateInputDevices() {
  1028. input_devices = input_subsystem->GetInputDevices();
  1029. ui->comboDevices->clear();
  1030. for (const auto& device : input_devices) {
  1031. ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
  1032. }
  1033. }
  1034. void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
  1035. auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
  1036. if (debug) {
  1037. layout = Core::HID::NpadStyleIndex::ProController;
  1038. }
  1039. // List of all the widgets that will be hidden by any of the following layouts that need
  1040. // "unhidden" after the controller type changes
  1041. const std::array<QWidget*, 11> layout_show = {
  1042. ui->buttonShoulderButtonsSLSR,
  1043. ui->horizontalSpacerShoulderButtonsWidget,
  1044. ui->horizontalSpacerShoulderButtonsWidget2,
  1045. ui->buttonShoulderButtonsLeft,
  1046. ui->buttonMiscButtonsMinusScreenshot,
  1047. ui->bottomLeft,
  1048. ui->buttonShoulderButtonsRight,
  1049. ui->buttonMiscButtonsPlusHome,
  1050. ui->bottomRight,
  1051. ui->buttonMiscButtonsMinusGroup,
  1052. ui->buttonMiscButtonsScreenshotGroup,
  1053. };
  1054. for (auto* widget : layout_show) {
  1055. widget->show();
  1056. }
  1057. std::vector<QWidget*> layout_hidden;
  1058. switch (layout) {
  1059. case Core::HID::NpadStyleIndex::ProController:
  1060. case Core::HID::NpadStyleIndex::JoyconDual:
  1061. case Core::HID::NpadStyleIndex::Handheld:
  1062. layout_hidden = {
  1063. ui->buttonShoulderButtonsSLSR,
  1064. ui->horizontalSpacerShoulderButtonsWidget2,
  1065. };
  1066. break;
  1067. case Core::HID::NpadStyleIndex::JoyconLeft:
  1068. layout_hidden = {
  1069. ui->horizontalSpacerShoulderButtonsWidget2,
  1070. ui->buttonShoulderButtonsRight,
  1071. ui->buttonMiscButtonsPlusHome,
  1072. ui->bottomRight,
  1073. };
  1074. break;
  1075. case Core::HID::NpadStyleIndex::JoyconRight:
  1076. layout_hidden = {
  1077. ui->horizontalSpacerShoulderButtonsWidget,
  1078. ui->buttonShoulderButtonsLeft,
  1079. ui->buttonMiscButtonsMinusScreenshot,
  1080. ui->bottomLeft,
  1081. };
  1082. break;
  1083. case Core::HID::NpadStyleIndex::GameCube:
  1084. layout_hidden = {
  1085. ui->buttonShoulderButtonsSLSR,
  1086. ui->horizontalSpacerShoulderButtonsWidget2,
  1087. ui->buttonMiscButtonsMinusGroup,
  1088. ui->buttonMiscButtonsScreenshotGroup,
  1089. };
  1090. break;
  1091. default:
  1092. break;
  1093. }
  1094. for (auto* widget : layout_hidden) {
  1095. widget->hide();
  1096. }
  1097. }
  1098. void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
  1099. auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
  1100. if (debug) {
  1101. layout = Core::HID::NpadStyleIndex::ProController;
  1102. }
  1103. // List of all the widgets that will be disabled by any of the following layouts that need
  1104. // "enabled" after the controller type changes
  1105. const std::array<QWidget*, 3> layout_enable = {
  1106. ui->buttonLStickPressedGroup,
  1107. ui->groupRStickPressed,
  1108. ui->buttonShoulderButtonsButtonLGroup,
  1109. };
  1110. for (auto* widget : layout_enable) {
  1111. widget->setEnabled(true);
  1112. }
  1113. std::vector<QWidget*> layout_disable;
  1114. switch (layout) {
  1115. case Core::HID::NpadStyleIndex::ProController:
  1116. case Core::HID::NpadStyleIndex::JoyconDual:
  1117. case Core::HID::NpadStyleIndex::Handheld:
  1118. case Core::HID::NpadStyleIndex::JoyconLeft:
  1119. case Core::HID::NpadStyleIndex::JoyconRight:
  1120. break;
  1121. case Core::HID::NpadStyleIndex::GameCube:
  1122. layout_disable = {
  1123. ui->buttonHome,
  1124. ui->buttonLStickPressedGroup,
  1125. ui->groupRStickPressed,
  1126. ui->buttonShoulderButtonsButtonLGroup,
  1127. };
  1128. break;
  1129. default:
  1130. break;
  1131. }
  1132. for (auto* widget : layout_disable) {
  1133. widget->setEnabled(false);
  1134. }
  1135. }
  1136. void ConfigureInputPlayer::UpdateMotionButtons() {
  1137. if (debug) {
  1138. // Motion isn't used with the debug controller, hide both groupboxes.
  1139. ui->buttonMotionLeftGroup->hide();
  1140. ui->buttonMotionRightGroup->hide();
  1141. return;
  1142. }
  1143. // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
  1144. switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
  1145. case Core::HID::NpadStyleIndex::ProController:
  1146. case Core::HID::NpadStyleIndex::JoyconLeft:
  1147. case Core::HID::NpadStyleIndex::Handheld:
  1148. // Show "Motion 1" and hide "Motion 2".
  1149. ui->buttonMotionLeftGroup->show();
  1150. ui->buttonMotionRightGroup->hide();
  1151. break;
  1152. case Core::HID::NpadStyleIndex::JoyconRight:
  1153. // Show "Motion 2" and hide "Motion 1".
  1154. ui->buttonMotionLeftGroup->hide();
  1155. ui->buttonMotionRightGroup->show();
  1156. break;
  1157. case Core::HID::NpadStyleIndex::GameCube:
  1158. // Hide both "Motion 1/2".
  1159. ui->buttonMotionLeftGroup->hide();
  1160. ui->buttonMotionRightGroup->hide();
  1161. break;
  1162. case Core::HID::NpadStyleIndex::JoyconDual:
  1163. default:
  1164. // Show both "Motion 1/2".
  1165. ui->buttonMotionLeftGroup->show();
  1166. ui->buttonMotionRightGroup->show();
  1167. break;
  1168. }
  1169. }
  1170. void ConfigureInputPlayer::UpdateControllerButtonNames() {
  1171. auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
  1172. if (debug) {
  1173. layout = Core::HID::NpadStyleIndex::ProController;
  1174. }
  1175. switch (layout) {
  1176. case Core::HID::NpadStyleIndex::ProController:
  1177. case Core::HID::NpadStyleIndex::JoyconDual:
  1178. case Core::HID::NpadStyleIndex::Handheld:
  1179. case Core::HID::NpadStyleIndex::JoyconLeft:
  1180. case Core::HID::NpadStyleIndex::JoyconRight:
  1181. ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus"));
  1182. ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL"));
  1183. ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR"));
  1184. ui->buttonShoulderButtonsRGroup->setTitle(tr("R"));
  1185. ui->LStick->setTitle(tr("Left Stick"));
  1186. ui->RStick->setTitle(tr("Right Stick"));
  1187. break;
  1188. case Core::HID::NpadStyleIndex::GameCube:
  1189. ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause"));
  1190. ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L"));
  1191. ui->buttonShoulderButtonsZRGroup->setTitle(tr("R"));
  1192. ui->buttonShoulderButtonsRGroup->setTitle(tr("Z"));
  1193. ui->LStick->setTitle(tr("Control Stick"));
  1194. ui->RStick->setTitle(tr("C-Stick"));
  1195. break;
  1196. default:
  1197. break;
  1198. }
  1199. }
  1200. void ConfigureInputPlayer::UpdateMappingWithDefaults() {
  1201. if (ui->comboDevices->currentIndex() == 0) {
  1202. return;
  1203. }
  1204. for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
  1205. const auto* const button = button_map[button_id];
  1206. if (button == nullptr) {
  1207. continue;
  1208. }
  1209. emulated_controller->SetButtonParam(button_id, {});
  1210. }
  1211. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  1212. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  1213. const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
  1214. if (analog_button == nullptr) {
  1215. continue;
  1216. }
  1217. emulated_controller->SetStickParam(analog_id, {});
  1218. }
  1219. }
  1220. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  1221. const auto* const motion_button = motion_map[motion_id];
  1222. if (motion_button == nullptr) {
  1223. continue;
  1224. }
  1225. emulated_controller->SetMotionParam(motion_id, {});
  1226. }
  1227. // Reset keyboard or mouse bindings
  1228. if (ui->comboDevices->currentIndex() == 1 || ui->comboDevices->currentIndex() == 2) {
  1229. for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
  1230. emulated_controller->SetButtonParam(
  1231. button_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam(
  1232. Config::default_buttons[button_id])});
  1233. }
  1234. for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
  1235. Common::ParamPackage analog_param{};
  1236. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  1237. Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
  1238. Config::default_analogs[analog_id][sub_button_id])};
  1239. SetAnalogParam(params, analog_param, analog_sub_buttons[sub_button_id]);
  1240. }
  1241. analog_param.Set("modifier", InputCommon::GenerateKeyboardParam(
  1242. Config::default_stick_mod[analog_id]));
  1243. emulated_controller->SetStickParam(analog_id, analog_param);
  1244. }
  1245. for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
  1246. emulated_controller->SetMotionParam(
  1247. motion_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam(
  1248. Config::default_motions[motion_id])});
  1249. }
  1250. // If mouse is selected we want to override with mappings from the driver
  1251. if (ui->comboDevices->currentIndex() == 1) {
  1252. UpdateUI();
  1253. return;
  1254. }
  1255. }
  1256. // Reset controller bindings
  1257. const auto& device = input_devices[ui->comboDevices->currentIndex()];
  1258. auto button_mappings = input_subsystem->GetButtonMappingForDevice(device);
  1259. auto analog_mappings = input_subsystem->GetAnalogMappingForDevice(device);
  1260. auto motion_mappings = input_subsystem->GetMotionMappingForDevice(device);
  1261. for (const auto& button_mapping : button_mappings) {
  1262. const std::size_t index = button_mapping.first;
  1263. emulated_controller->SetButtonParam(index, button_mapping.second);
  1264. }
  1265. for (const auto& analog_mapping : analog_mappings) {
  1266. const std::size_t index = analog_mapping.first;
  1267. emulated_controller->SetStickParam(index, analog_mapping.second);
  1268. }
  1269. for (const auto& motion_mapping : motion_mappings) {
  1270. const std::size_t index = motion_mapping.first;
  1271. emulated_controller->SetMotionParam(index, motion_mapping.second);
  1272. }
  1273. UpdateUI();
  1274. }
  1275. void ConfigureInputPlayer::HandleClick(
  1276. QPushButton* button, std::size_t button_id,
  1277. std::function<void(const Common::ParamPackage&)> new_input_setter,
  1278. InputCommon::Polling::InputType type) {
  1279. if (timeout_timer->isActive()) {
  1280. return;
  1281. }
  1282. if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
  1283. button->setText(tr("Shake!"));
  1284. } else {
  1285. button->setText(tr("[waiting]"));
  1286. }
  1287. button->setFocus();
  1288. input_setter = std::move(new_input_setter);
  1289. input_subsystem->BeginMapping(type);
  1290. QWidget::grabMouse();
  1291. QWidget::grabKeyboard();
  1292. if (type == InputCommon::Polling::InputType::Button) {
  1293. ui->controllerFrame->BeginMappingButton(button_id);
  1294. } else if (type == InputCommon::Polling::InputType::Stick) {
  1295. ui->controllerFrame->BeginMappingAnalog(button_id);
  1296. }
  1297. timeout_timer->start(4000); // Cancel after 4 seconds
  1298. poll_timer->start(25); // Check for new inputs every 25ms
  1299. }
  1300. void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
  1301. timeout_timer->stop();
  1302. poll_timer->stop();
  1303. input_subsystem->StopMapping();
  1304. QWidget::releaseMouse();
  1305. QWidget::releaseKeyboard();
  1306. if (!abort) {
  1307. (*input_setter)(params);
  1308. }
  1309. UpdateUI();
  1310. UpdateInputDeviceCombobox();
  1311. ui->controllerFrame->EndMapping();
  1312. input_setter = std::nullopt;
  1313. }
  1314. bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const {
  1315. if (ui->comboDevices->currentIndex() == 0) {
  1316. return true;
  1317. }
  1318. if (params.Has("motion")) {
  1319. return true;
  1320. }
  1321. // Keyboard/Mouse
  1322. if (ui->comboDevices->currentIndex() == 1 || ui->comboDevices->currentIndex() == 2) {
  1323. return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse";
  1324. }
  1325. const auto& current_input_device = input_devices[ui->comboDevices->currentIndex()];
  1326. return params.Get("engine", "") == current_input_device.Get("engine", "") &&
  1327. (params.Get("guid", "") == current_input_device.Get("guid", "") ||
  1328. params.Get("guid", "") == current_input_device.Get("guid2", "")) &&
  1329. params.Get("port", 0) == current_input_device.Get("port", 0);
  1330. }
  1331. void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
  1332. if (!input_setter || !event) {
  1333. return;
  1334. }
  1335. const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
  1336. input_subsystem->GetMouse()->PressButton(0, 0, button);
  1337. }
  1338. void ConfigureInputPlayer::wheelEvent(QWheelEvent* event) {
  1339. const int x = event->angleDelta().x();
  1340. const int y = event->angleDelta().y();
  1341. input_subsystem->GetMouse()->MouseWheelChange(x, y);
  1342. }
  1343. void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
  1344. if (!input_setter || !event) {
  1345. return;
  1346. }
  1347. event->ignore();
  1348. if (event->key() != Qt::Key_Escape) {
  1349. input_subsystem->GetKeyboard()->PressKey(event->key());
  1350. }
  1351. }
  1352. bool ConfigureInputPlayer::eventFilter(QObject* object, QEvent* event) {
  1353. if (object == ui->comboDevices && event->type() == QEvent::MouseButtonPress) {
  1354. RefreshInputDevices();
  1355. }
  1356. return object->eventFilter(object, event);
  1357. }
  1358. void ConfigureInputPlayer::CreateProfile() {
  1359. const auto profile_name =
  1360. LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 30,
  1361. LimitableInputDialog::InputLimiter::Filesystem);
  1362. if (profile_name.isEmpty()) {
  1363. return;
  1364. }
  1365. if (!InputProfiles::IsProfileNameValid(profile_name.toStdString())) {
  1366. QMessageBox::critical(this, tr("Create Input Profile"),
  1367. tr("The given profile name is not valid!"));
  1368. return;
  1369. }
  1370. ApplyConfiguration();
  1371. if (!profiles->CreateProfile(profile_name.toStdString(), player_index)) {
  1372. QMessageBox::critical(this, tr("Create Input Profile"),
  1373. tr("Failed to create the input profile \"%1\"").arg(profile_name));
  1374. UpdateInputProfiles();
  1375. emit RefreshInputProfiles(player_index);
  1376. return;
  1377. }
  1378. emit RefreshInputProfiles(player_index);
  1379. ui->comboProfiles->addItem(profile_name);
  1380. ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1);
  1381. }
  1382. void ConfigureInputPlayer::DeleteProfile() {
  1383. const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
  1384. if (profile_name.isEmpty()) {
  1385. return;
  1386. }
  1387. if (!profiles->DeleteProfile(profile_name.toStdString())) {
  1388. QMessageBox::critical(this, tr("Delete Input Profile"),
  1389. tr("Failed to delete the input profile \"%1\"").arg(profile_name));
  1390. UpdateInputProfiles();
  1391. emit RefreshInputProfiles(player_index);
  1392. return;
  1393. }
  1394. emit RefreshInputProfiles(player_index);
  1395. ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex());
  1396. ui->comboProfiles->setCurrentIndex(-1);
  1397. }
  1398. void ConfigureInputPlayer::LoadProfile() {
  1399. const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
  1400. if (profile_name.isEmpty()) {
  1401. return;
  1402. }
  1403. ApplyConfiguration();
  1404. if (!profiles->LoadProfile(profile_name.toStdString(), player_index)) {
  1405. QMessageBox::critical(this, tr("Load Input Profile"),
  1406. tr("Failed to load the input profile \"%1\"").arg(profile_name));
  1407. UpdateInputProfiles();
  1408. emit RefreshInputProfiles(player_index);
  1409. return;
  1410. }
  1411. LoadConfiguration();
  1412. }
  1413. void ConfigureInputPlayer::SaveProfile() {
  1414. static constexpr size_t HANDHELD_INDEX = 8;
  1415. const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
  1416. if (profile_name.isEmpty()) {
  1417. return;
  1418. }
  1419. ApplyConfiguration();
  1420. // When we're in handheld mode, only the handheld emulated controller bindings are updated
  1421. const bool is_handheld = player_index == 0 && emulated_controller->GetNpadIdType() ==
  1422. Core::HID::NpadIdType::Handheld;
  1423. const auto profile_player_index = is_handheld ? HANDHELD_INDEX : player_index;
  1424. if (!profiles->SaveProfile(profile_name.toStdString(), profile_player_index)) {
  1425. QMessageBox::critical(this, tr("Save Input Profile"),
  1426. tr("Failed to save the input profile \"%1\"").arg(profile_name));
  1427. UpdateInputProfiles();
  1428. emit RefreshInputProfiles(player_index);
  1429. return;
  1430. }
  1431. }
  1432. void ConfigureInputPlayer::UpdateInputProfiles() {
  1433. ui->comboProfiles->clear();
  1434. for (const auto& profile_name : profiles->GetInputProfileNames()) {
  1435. ui->comboProfiles->addItem(QString::fromStdString(profile_name));
  1436. }
  1437. ui->comboProfiles->setCurrentIndex(-1);
  1438. }