configure_motion_touch.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. // Copyright 2018 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <array>
  5. #include <QCloseEvent>
  6. #include <QLabel>
  7. #include <QMessageBox>
  8. #include <QPushButton>
  9. #include <QVBoxLayout>
  10. #include "common/logging/log.h"
  11. #include "core/settings.h"
  12. #include "input_common/main.h"
  13. #include "input_common/udp/client.h"
  14. #include "input_common/udp/udp.h"
  15. #include "ui_configure_motion_touch.h"
  16. #include "yuzu/configuration/configure_motion_touch.h"
  17. #include "yuzu/configuration/configure_touch_from_button.h"
  18. CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
  19. const std::string& host, u16 port,
  20. u8 pad_index, u16 client_id)
  21. : QDialog(parent) {
  22. layout = new QVBoxLayout;
  23. status_label = new QLabel(tr("Communicating with the server..."));
  24. cancel_button = new QPushButton(tr("Cancel"));
  25. connect(cancel_button, &QPushButton::clicked, this, [this] {
  26. if (!completed) {
  27. job->Stop();
  28. }
  29. accept();
  30. });
  31. layout->addWidget(status_label);
  32. layout->addWidget(cancel_button);
  33. setLayout(layout);
  34. using namespace InputCommon::CemuhookUDP;
  35. job = std::make_unique<CalibrationConfigurationJob>(
  36. host, port, pad_index, client_id,
  37. [this](CalibrationConfigurationJob::Status status) {
  38. QString text;
  39. switch (status) {
  40. case CalibrationConfigurationJob::Status::Ready:
  41. text = tr("Touch the top left corner <br>of your touchpad.");
  42. break;
  43. case CalibrationConfigurationJob::Status::Stage1Completed:
  44. text = tr("Now touch the bottom right corner <br>of your touchpad.");
  45. break;
  46. case CalibrationConfigurationJob::Status::Completed:
  47. text = tr("Configuration completed!");
  48. break;
  49. }
  50. QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
  51. if (status == CalibrationConfigurationJob::Status::Completed) {
  52. QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK")));
  53. }
  54. },
  55. [this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
  56. completed = true;
  57. min_x = min_x_;
  58. min_y = min_y_;
  59. max_x = max_x_;
  60. max_y = max_y_;
  61. });
  62. }
  63. CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default;
  64. void CalibrationConfigurationDialog::UpdateLabelText(const QString& text) {
  65. status_label->setText(text);
  66. }
  67. void CalibrationConfigurationDialog::UpdateButtonText(const QString& text) {
  68. cancel_button->setText(text);
  69. }
  70. constexpr std::array<std::pair<const char*, const char*>, 2> MotionProviders = {{
  71. {"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")},
  72. {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
  73. }};
  74. constexpr std::array<std::pair<const char*, const char*>, 2> TouchProviders = {{
  75. {"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")},
  76. {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
  77. }};
  78. ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent,
  79. InputCommon::InputSubsystem* input_subsystem_)
  80. : QDialog(parent), input_subsystem{input_subsystem_},
  81. ui(std::make_unique<Ui::ConfigureMotionTouch>()) {
  82. ui->setupUi(this);
  83. for (const auto& [provider, name] : MotionProviders) {
  84. ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider));
  85. }
  86. for (const auto& [provider, name] : TouchProviders) {
  87. ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider));
  88. }
  89. ui->udp_learn_more->setOpenExternalLinks(true);
  90. ui->udp_learn_more->setText(
  91. tr("<a "
  92. "href='https://yuzu-emu.org/wiki/"
  93. "using-a-controller-or-android-phone-for-motion-or-touch-input'><span "
  94. "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
  95. SetConfiguration();
  96. UpdateUiDisplay();
  97. ConnectEvents();
  98. }
  99. ConfigureMotionTouch::~ConfigureMotionTouch() = default;
  100. void ConfigureMotionTouch::SetConfiguration() {
  101. const Common::ParamPackage motion_param(Settings::values.motion_device);
  102. const Common::ParamPackage touch_param(Settings::values.touch_device);
  103. const std::string motion_engine = motion_param.Get("engine", "motion_emu");
  104. const std::string touch_engine = touch_param.Get("engine", "emu_window");
  105. ui->motion_provider->setCurrentIndex(
  106. ui->motion_provider->findData(QString::fromStdString(motion_engine)));
  107. ui->touch_provider->setCurrentIndex(
  108. ui->touch_provider->findData(QString::fromStdString(touch_engine)));
  109. ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button);
  110. touch_from_button_maps = Settings::values.touch_from_button_maps;
  111. for (const auto& touch_map : touch_from_button_maps) {
  112. ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
  113. }
  114. ui->touch_from_button_map->setCurrentIndex(Settings::values.touch_from_button_map_index);
  115. ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f));
  116. min_x = touch_param.Get("min_x", 100);
  117. min_y = touch_param.Get("min_y", 50);
  118. max_x = touch_param.Get("max_x", 1800);
  119. max_y = touch_param.Get("max_y", 850);
  120. ui->udp_server->setText(QString::fromStdString(Settings::values.udp_input_address));
  121. ui->udp_port->setText(QString::number(Settings::values.udp_input_port));
  122. ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index);
  123. }
  124. void ConfigureMotionTouch::UpdateUiDisplay() {
  125. const QString motion_engine = ui->motion_provider->currentData().toString();
  126. const QString touch_engine = ui->touch_provider->currentData().toString();
  127. const QString cemuhook_udp = QStringLiteral("cemuhookudp");
  128. if (motion_engine == QStringLiteral("motion_emu")) {
  129. ui->motion_sensitivity_label->setVisible(true);
  130. ui->motion_sensitivity->setVisible(true);
  131. } else {
  132. ui->motion_sensitivity_label->setVisible(false);
  133. ui->motion_sensitivity->setVisible(false);
  134. }
  135. if (touch_engine == cemuhook_udp) {
  136. ui->touch_calibration->setVisible(true);
  137. ui->touch_calibration_config->setVisible(true);
  138. ui->touch_calibration_label->setVisible(true);
  139. ui->touch_calibration->setText(
  140. QStringLiteral("(%1, %2) - (%3, %4)").arg(min_x).arg(min_y).arg(max_x).arg(max_y));
  141. } else {
  142. ui->touch_calibration->setVisible(false);
  143. ui->touch_calibration_config->setVisible(false);
  144. ui->touch_calibration_label->setVisible(false);
  145. }
  146. if (motion_engine == cemuhook_udp || touch_engine == cemuhook_udp) {
  147. ui->udp_config_group_box->setVisible(true);
  148. } else {
  149. ui->udp_config_group_box->setVisible(false);
  150. }
  151. }
  152. void ConfigureMotionTouch::ConnectEvents() {
  153. connect(ui->motion_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
  154. [this](int index) { UpdateUiDisplay(); });
  155. connect(ui->touch_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
  156. [this](int index) { UpdateUiDisplay(); });
  157. connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest);
  158. connect(ui->touch_calibration_config, &QPushButton::clicked, this,
  159. &ConfigureMotionTouch::OnConfigureTouchCalibration);
  160. connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
  161. &ConfigureMotionTouch::OnConfigureTouchFromButton);
  162. connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
  163. if (CanCloseDialog()) {
  164. reject();
  165. }
  166. });
  167. }
  168. void ConfigureMotionTouch::OnCemuhookUDPTest() {
  169. ui->udp_test->setEnabled(false);
  170. ui->udp_test->setText(tr("Testing"));
  171. udp_test_in_progress = true;
  172. InputCommon::CemuhookUDP::TestCommunication(
  173. ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()),
  174. static_cast<u32>(ui->udp_pad_index->currentIndex()), 24872,
  175. [this] {
  176. LOG_INFO(Frontend, "UDP input test success");
  177. QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true));
  178. },
  179. [this] {
  180. LOG_ERROR(Frontend, "UDP input test failed");
  181. QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false));
  182. });
  183. }
  184. void ConfigureMotionTouch::OnConfigureTouchCalibration() {
  185. ui->touch_calibration_config->setEnabled(false);
  186. ui->touch_calibration_config->setText(tr("Configuring"));
  187. CalibrationConfigurationDialog dialog(
  188. this, ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toUInt()),
  189. static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872);
  190. dialog.exec();
  191. if (dialog.completed) {
  192. min_x = dialog.min_x;
  193. min_y = dialog.min_y;
  194. max_x = dialog.max_x;
  195. max_y = dialog.max_y;
  196. LOG_INFO(Frontend,
  197. "UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}",
  198. min_x, min_y, max_x, max_y);
  199. UpdateUiDisplay();
  200. } else {
  201. LOG_ERROR(Frontend, "UDP touchpad calibration config failed");
  202. }
  203. ui->touch_calibration_config->setEnabled(true);
  204. ui->touch_calibration_config->setText(tr("Configure"));
  205. }
  206. void ConfigureMotionTouch::closeEvent(QCloseEvent* event) {
  207. if (CanCloseDialog()) {
  208. event->accept();
  209. } else {
  210. event->ignore();
  211. }
  212. }
  213. void ConfigureMotionTouch::ShowUDPTestResult(bool result) {
  214. udp_test_in_progress = false;
  215. if (result) {
  216. QMessageBox::information(this, tr("Test Successful"),
  217. tr("Successfully received data from the server."));
  218. } else {
  219. QMessageBox::warning(this, tr("Test Failed"),
  220. tr("Could not receive valid data from the server.<br>Please verify "
  221. "that the server is set up correctly and "
  222. "the address and port are correct."));
  223. }
  224. ui->udp_test->setEnabled(true);
  225. ui->udp_test->setText(tr("Test"));
  226. }
  227. void ConfigureMotionTouch::OnConfigureTouchFromButton() {
  228. ConfigureTouchFromButton dialog{this, touch_from_button_maps, input_subsystem,
  229. ui->touch_from_button_map->currentIndex()};
  230. if (dialog.exec() != QDialog::Accepted) {
  231. return;
  232. }
  233. touch_from_button_maps = dialog.GetMaps();
  234. while (ui->touch_from_button_map->count() > 0) {
  235. ui->touch_from_button_map->removeItem(0);
  236. }
  237. for (const auto& touch_map : touch_from_button_maps) {
  238. ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
  239. }
  240. ui->touch_from_button_map->setCurrentIndex(dialog.GetSelectedIndex());
  241. }
  242. bool ConfigureMotionTouch::CanCloseDialog() {
  243. if (udp_test_in_progress) {
  244. QMessageBox::warning(this, tr("Citra"),
  245. tr("UDP Test or calibration configuration is in progress.<br>Please "
  246. "wait for them to finish."));
  247. return false;
  248. }
  249. return true;
  250. }
  251. void ConfigureMotionTouch::ApplyConfiguration() {
  252. if (!CanCloseDialog()) {
  253. return;
  254. }
  255. std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
  256. std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
  257. Common::ParamPackage motion_param{}, touch_param{};
  258. motion_param.Set("engine", std::move(motion_engine));
  259. touch_param.Set("engine", std::move(touch_engine));
  260. if (motion_engine == "motion_emu") {
  261. motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value()));
  262. }
  263. if (touch_engine == "cemuhookudp") {
  264. touch_param.Set("min_x", min_x);
  265. touch_param.Set("min_y", min_y);
  266. touch_param.Set("max_x", max_x);
  267. touch_param.Set("max_y", max_y);
  268. }
  269. Settings::values.motion_device = motion_param.Serialize();
  270. Settings::values.touch_device = touch_param.Serialize();
  271. Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked();
  272. Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex();
  273. Settings::values.touch_from_button_maps = touch_from_button_maps;
  274. Settings::values.udp_input_address = ui->udp_server->text().toStdString();
  275. Settings::values.udp_input_port = static_cast<u16>(ui->udp_port->text().toInt());
  276. Settings::values.udp_pad_index = static_cast<u8>(ui->udp_pad_index->currentIndex());
  277. input_subsystem->ReloadInputDevices();
  278. accept();
  279. }