configure_ringcon.cpp 15 KB

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