configure_ringcon.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <memory>
  4. #include <QKeyEvent>
  5. #include <QMenu>
  6. #include <QMessageBox>
  7. #include <QTimer>
  8. #include <fmt/format.h>
  9. #include "core/hid/emulated_controller.h"
  10. #include "core/hid/hid_core.h"
  11. #include "input_common/drivers/keyboard.h"
  12. #include "input_common/drivers/mouse.h"
  13. #include "input_common/main.h"
  14. #include "ui_configure_ringcon.h"
  15. #include "yuzu/bootmanager.h"
  16. #include "yuzu/configuration/config.h"
  17. #include "yuzu/configuration/configure_ringcon.h"
  18. const std::array<std::string, ConfigureRingController::ANALOG_SUB_BUTTONS_NUM>
  19. ConfigureRingController::analog_sub_buttons{{
  20. "left",
  21. "right",
  22. }};
  23. namespace {
  24. QString GetKeyName(int key_code) {
  25. switch (key_code) {
  26. case Qt::Key_Shift:
  27. return QObject::tr("Shift");
  28. case Qt::Key_Control:
  29. return QObject::tr("Ctrl");
  30. case Qt::Key_Alt:
  31. return QObject::tr("Alt");
  32. case Qt::Key_Meta:
  33. return {};
  34. default:
  35. return QKeySequence(key_code).toString();
  36. }
  37. }
  38. QString GetButtonName(Common::Input::ButtonNames button_name) {
  39. switch (button_name) {
  40. case Common::Input::ButtonNames::ButtonLeft:
  41. return QObject::tr("Left");
  42. case Common::Input::ButtonNames::ButtonRight:
  43. return QObject::tr("Right");
  44. case Common::Input::ButtonNames::ButtonDown:
  45. return QObject::tr("Down");
  46. case Common::Input::ButtonNames::ButtonUp:
  47. return QObject::tr("Up");
  48. case Common::Input::ButtonNames::TriggerZ:
  49. return QObject::tr("Z");
  50. case Common::Input::ButtonNames::TriggerR:
  51. return QObject::tr("R");
  52. case Common::Input::ButtonNames::TriggerL:
  53. return QObject::tr("L");
  54. case Common::Input::ButtonNames::ButtonA:
  55. return QObject::tr("A");
  56. case Common::Input::ButtonNames::ButtonB:
  57. return QObject::tr("B");
  58. case Common::Input::ButtonNames::ButtonX:
  59. return QObject::tr("X");
  60. case Common::Input::ButtonNames::ButtonY:
  61. return QObject::tr("Y");
  62. case Common::Input::ButtonNames::ButtonStart:
  63. return QObject::tr("Start");
  64. case Common::Input::ButtonNames::L1:
  65. return QObject::tr("L1");
  66. case Common::Input::ButtonNames::L2:
  67. return QObject::tr("L2");
  68. case Common::Input::ButtonNames::L3:
  69. return QObject::tr("L3");
  70. case Common::Input::ButtonNames::R1:
  71. return QObject::tr("R1");
  72. case Common::Input::ButtonNames::R2:
  73. return QObject::tr("R2");
  74. case Common::Input::ButtonNames::R3:
  75. return QObject::tr("R3");
  76. case Common::Input::ButtonNames::Circle:
  77. return QObject::tr("Circle");
  78. case Common::Input::ButtonNames::Cross:
  79. return QObject::tr("Cross");
  80. case Common::Input::ButtonNames::Square:
  81. return QObject::tr("Square");
  82. case Common::Input::ButtonNames::Triangle:
  83. return QObject::tr("Triangle");
  84. case Common::Input::ButtonNames::Share:
  85. return QObject::tr("Share");
  86. case Common::Input::ButtonNames::Options:
  87. return QObject::tr("Options");
  88. default:
  89. return QObject::tr("[undefined]");
  90. }
  91. }
  92. void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param,
  93. const std::string& button_name) {
  94. // The poller returned a complete axis, so set all the buttons
  95. if (input_param.Has("axis_x") && input_param.Has("axis_y")) {
  96. analog_param = input_param;
  97. return;
  98. }
  99. // Check if the current configuration has either no engine or an axis binding.
  100. // Clears out the old binding and adds one with analog_from_button.
  101. if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) {
  102. analog_param = {
  103. {"engine", "analog_from_button"},
  104. };
  105. }
  106. analog_param.Set(button_name, input_param.Serialize());
  107. }
  108. } // namespace
  109. ConfigureRingController::ConfigureRingController(QWidget* parent,
  110. InputCommon::InputSubsystem* input_subsystem_,
  111. Core::HID::HIDCore& hid_core_)
  112. : QDialog(parent), timeout_timer(std::make_unique<QTimer>()),
  113. poll_timer(std::make_unique<QTimer>()), input_subsystem{input_subsystem_},
  114. ui(std::make_unique<Ui::ConfigureRingController>()) {
  115. ui->setupUi(this);
  116. analog_map_buttons = {
  117. ui->buttonRingAnalogPull,
  118. ui->buttonRingAnalogPush,
  119. };
  120. emulated_controller = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
  121. emulated_controller->SaveCurrentConfig();
  122. emulated_controller->EnableConfiguration();
  123. Core::HID::ControllerUpdateCallback engine_callback{
  124. .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); },
  125. .is_npad_service = false,
  126. };
  127. callback_key = emulated_controller->SetCallback(engine_callback);
  128. is_controller_set = true;
  129. LoadConfiguration();
  130. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  131. auto* const analog_button = analog_map_buttons[sub_button_id];
  132. if (analog_button == nullptr) {
  133. continue;
  134. }
  135. connect(analog_button, &QPushButton::clicked, [=, this] {
  136. HandleClick(
  137. analog_map_buttons[sub_button_id],
  138. [=, this](const Common::ParamPackage& params) {
  139. Common::ParamPackage param = emulated_controller->GetRingParam();
  140. SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
  141. emulated_controller->SetRingParam(param);
  142. },
  143. InputCommon::Polling::InputType::Stick);
  144. });
  145. analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
  146. connect(analog_button, &QPushButton::customContextMenuRequested,
  147. [=, this](const QPoint& menu_location) {
  148. QMenu context_menu;
  149. Common::ParamPackage param = emulated_controller->GetRingParam();
  150. context_menu.addAction(tr("Clear"), [&] {
  151. emulated_controller->SetRingParam(param);
  152. analog_map_buttons[sub_button_id]->setText(tr("[not set]"));
  153. });
  154. context_menu.addAction(tr("Invert axis"), [&] {
  155. const bool invert_value = param.Get("invert_x", "+") == "-";
  156. const std::string invert_str = invert_value ? "+" : "-";
  157. param.Set("invert_x", invert_str);
  158. emulated_controller->SetRingParam(param);
  159. for (int sub_button_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM;
  160. ++sub_button_id2) {
  161. analog_map_buttons[sub_button_id2]->setText(
  162. AnalogToText(param, analog_sub_buttons[sub_button_id2]));
  163. }
  164. });
  165. context_menu.exec(
  166. analog_map_buttons[sub_button_id]->mapToGlobal(menu_location));
  167. });
  168. }
  169. connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] {
  170. Common::ParamPackage param = emulated_controller->GetRingParam();
  171. const auto slider_value = ui->sliderRingAnalogDeadzone->value();
  172. ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value));
  173. param.Set("deadzone", slider_value / 100.0f);
  174. emulated_controller->SetRingParam(param);
  175. });
  176. connect(ui->restore_defaults_button, &QPushButton::clicked, this,
  177. &ConfigureRingController::RestoreDefaults);
  178. connect(ui->enable_ring_controller_button, &QPushButton::clicked, this,
  179. &ConfigureRingController::EnableRingController);
  180. timeout_timer->setSingleShot(true);
  181. connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
  182. connect(poll_timer.get(), &QTimer::timeout, [this] {
  183. const auto& params = input_subsystem->GetNextInput();
  184. if (params.Has("engine") && IsInputAcceptable(params)) {
  185. SetPollingResult(params, false);
  186. return;
  187. }
  188. });
  189. resize(0, 0);
  190. }
  191. ConfigureRingController::~ConfigureRingController() {
  192. emulated_controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
  193. Common::Input::PollingMode::Active);
  194. emulated_controller->DisableConfiguration();
  195. if (is_controller_set) {
  196. emulated_controller->DeleteCallback(callback_key);
  197. is_controller_set = false;
  198. }
  199. };
  200. void ConfigureRingController::changeEvent(QEvent* event) {
  201. if (event->type() == QEvent::LanguageChange) {
  202. RetranslateUI();
  203. }
  204. QDialog::changeEvent(event);
  205. }
  206. void ConfigureRingController::RetranslateUI() {
  207. ui->retranslateUi(this);
  208. }
  209. void ConfigureRingController::UpdateUI() {
  210. RetranslateUI();
  211. const Common::ParamPackage param = emulated_controller->GetRingParam();
  212. for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
  213. auto* const analog_button = analog_map_buttons[sub_button_id];
  214. if (analog_button == nullptr) {
  215. continue;
  216. }
  217. analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id]));
  218. }
  219. const auto deadzone_label = ui->labelRingAnalogDeadzone;
  220. const auto deadzone_slider = ui->sliderRingAnalogDeadzone;
  221. int slider_value = static_cast<int>(param.Get("deadzone", 0.15f) * 100);
  222. deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value));
  223. deadzone_slider->setValue(slider_value);
  224. }
  225. void ConfigureRingController::ApplyConfiguration() {
  226. emulated_controller->DisableConfiguration();
  227. emulated_controller->SaveCurrentConfig();
  228. emulated_controller->EnableConfiguration();
  229. }
  230. void ConfigureRingController::LoadConfiguration() {
  231. UpdateUI();
  232. }
  233. void ConfigureRingController::RestoreDefaults() {
  234. const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(
  235. 0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f);
  236. emulated_controller->SetRingParam(Common::ParamPackage(default_ring_string));
  237. UpdateUI();
  238. }
  239. void ConfigureRingController::EnableRingController() {
  240. const auto dialog_title = tr("Error enabling ring input");
  241. is_ring_enabled = false;
  242. ui->ring_controller_sensor_value->setText(tr("Not connected"));
  243. if (!Settings::values.enable_joycon_driver) {
  244. QMessageBox::warning(this, dialog_title, tr("Direct Joycon driver is not enabled"));
  245. return;
  246. }
  247. ui->enable_ring_controller_button->setEnabled(false);
  248. ui->enable_ring_controller_button->setText(tr("Configuring"));
  249. // SetPollingMode is blocking. Allow to update the button status before calling the command
  250. repaint();
  251. const auto result = emulated_controller->SetPollingMode(
  252. Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::Ring);
  253. switch (result) {
  254. case Common::Input::DriverResult::Success:
  255. is_ring_enabled = true;
  256. break;
  257. case Common::Input::DriverResult::NotSupported:
  258. QMessageBox::warning(this, dialog_title,
  259. tr("The current mapped device doesn't support the ring controller"));
  260. break;
  261. case Common::Input::DriverResult::NoDeviceDetected:
  262. QMessageBox::warning(this, dialog_title,
  263. tr("The current mapped device doesn't have a ring attached"));
  264. break;
  265. case Common::Input::DriverResult::InvalidHandle:
  266. QMessageBox::warning(this, dialog_title, tr("The current mapped device is not connected"));
  267. break;
  268. default:
  269. QMessageBox::warning(this, dialog_title,
  270. tr("Unexpected driver result %1").arg(static_cast<int>(result)));
  271. break;
  272. }
  273. ui->enable_ring_controller_button->setEnabled(true);
  274. ui->enable_ring_controller_button->setText(tr("Enable"));
  275. }
  276. void ConfigureRingController::ControllerUpdate(Core::HID::ControllerTriggerType type) {
  277. if (!is_ring_enabled) {
  278. return;
  279. }
  280. if (type != Core::HID::ControllerTriggerType::RingController) {
  281. return;
  282. }
  283. const auto value = emulated_controller->GetRingSensorValues();
  284. const auto tex_value = QString::fromStdString(fmt::format("{:.3f}", value.raw_value));
  285. ui->ring_controller_sensor_value->setText(tex_value);
  286. }
  287. void ConfigureRingController::HandleClick(
  288. QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
  289. InputCommon::Polling::InputType type) {
  290. button->setText(tr("[waiting]"));
  291. button->setFocus();
  292. input_setter = new_input_setter;
  293. input_subsystem->BeginMapping(type);
  294. QWidget::grabMouse();
  295. QWidget::grabKeyboard();
  296. timeout_timer->start(2500); // Cancel after 2.5 seconds
  297. poll_timer->start(25); // Check for new inputs every 25ms
  298. }
  299. void ConfigureRingController::SetPollingResult(const Common::ParamPackage& params, bool abort) {
  300. timeout_timer->stop();
  301. poll_timer->stop();
  302. input_subsystem->StopMapping();
  303. QWidget::releaseMouse();
  304. QWidget::releaseKeyboard();
  305. if (!abort) {
  306. (*input_setter)(params);
  307. }
  308. UpdateUI();
  309. input_setter = std::nullopt;
  310. }
  311. bool ConfigureRingController::IsInputAcceptable(const Common::ParamPackage& params) const {
  312. return true;
  313. }
  314. void ConfigureRingController::mousePressEvent(QMouseEvent* event) {
  315. if (!input_setter || !event) {
  316. return;
  317. }
  318. const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
  319. input_subsystem->GetMouse()->PressButton(0, 0, button);
  320. }
  321. void ConfigureRingController::keyPressEvent(QKeyEvent* event) {
  322. if (!input_setter || !event) {
  323. return;
  324. }
  325. event->ignore();
  326. if (event->key() != Qt::Key_Escape) {
  327. input_subsystem->GetKeyboard()->PressKey(event->key());
  328. }
  329. }
  330. QString ConfigureRingController::ButtonToText(const Common::ParamPackage& param) {
  331. if (!param.Has("engine")) {
  332. return QObject::tr("[not set]");
  333. }
  334. const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
  335. const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : "");
  336. const auto common_button_name = input_subsystem->GetButtonName(param);
  337. // Retrieve the names from Qt
  338. if (param.Get("engine", "") == "keyboard") {
  339. const QString button_str = GetKeyName(param.Get("code", 0));
  340. return QObject::tr("%1%2").arg(toggle, button_str);
  341. }
  342. if (common_button_name == Common::Input::ButtonNames::Invalid) {
  343. return QObject::tr("[invalid]");
  344. }
  345. if (common_button_name == Common::Input::ButtonNames::Engine) {
  346. return QString::fromStdString(param.Get("engine", ""));
  347. }
  348. if (common_button_name == Common::Input::ButtonNames::Value) {
  349. if (param.Has("hat")) {
  350. const QString hat = QString::fromStdString(param.Get("direction", ""));
  351. return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat);
  352. }
  353. if (param.Has("axis")) {
  354. const QString axis = QString::fromStdString(param.Get("axis", ""));
  355. return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis);
  356. }
  357. if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) {
  358. const QString axis_x = QString::fromStdString(param.Get("axis_x", ""));
  359. const QString axis_y = QString::fromStdString(param.Get("axis_y", ""));
  360. const QString axis_z = QString::fromStdString(param.Get("axis_z", ""));
  361. return QObject::tr("%1%2Axis %3,%4,%5").arg(toggle, inverted, axis_x, axis_y, axis_z);
  362. }
  363. if (param.Has("motion")) {
  364. const QString motion = QString::fromStdString(param.Get("motion", ""));
  365. return QObject::tr("%1%2Motion %3").arg(toggle, inverted, motion);
  366. }
  367. if (param.Has("button")) {
  368. const QString button = QString::fromStdString(param.Get("button", ""));
  369. return QObject::tr("%1%2Button %3").arg(toggle, inverted, button);
  370. }
  371. }
  372. QString button_name = GetButtonName(common_button_name);
  373. if (param.Has("hat")) {
  374. return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name);
  375. }
  376. if (param.Has("axis")) {
  377. return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
  378. }
  379. if (param.Has("motion")) {
  380. return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
  381. }
  382. if (param.Has("button")) {
  383. return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name);
  384. }
  385. return QObject::tr("[unknown]");
  386. }
  387. QString ConfigureRingController::AnalogToText(const Common::ParamPackage& param,
  388. const std::string& dir) {
  389. if (!param.Has("engine")) {
  390. return QObject::tr("[not set]");
  391. }
  392. if (param.Get("engine", "") == "analog_from_button") {
  393. return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
  394. }
  395. if (!param.Has("axis_x") || !param.Has("axis_y")) {
  396. return QObject::tr("[unknown]");
  397. }
  398. const auto engine_str = param.Get("engine", "");
  399. const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
  400. const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
  401. const bool invert_x = param.Get("invert_x", "+") == "-";
  402. const bool invert_y = param.Get("invert_y", "+") == "-";
  403. if (dir == "modifier") {
  404. return QObject::tr("[unused]");
  405. }
  406. if (dir == "left") {
  407. const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-");
  408. return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
  409. }
  410. if (dir == "right") {
  411. const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+");
  412. return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
  413. }
  414. if (dir == "up") {
  415. const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+");
  416. return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
  417. }
  418. if (dir == "down") {
  419. const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-");
  420. return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
  421. }
  422. return QObject::tr("[unknown]");
  423. }