configure_input_player.cpp 62 KB

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