NetPlaySetupDialog.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // Copyright 2017 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "DolphinQt/NetPlay/NetPlaySetupDialog.h"
  4. #include <memory>
  5. #include <QCheckBox>
  6. #include <QComboBox>
  7. #include <QDialogButtonBox>
  8. #include <QGridLayout>
  9. #include <QLabel>
  10. #include <QLineEdit>
  11. #include <QListWidget>
  12. #include <QPushButton>
  13. #include <QSignalBlocker>
  14. #include <QSpinBox>
  15. #include <QTabWidget>
  16. #include "Core/Config/NetplaySettings.h"
  17. #include "Core/NetPlayProto.h"
  18. #include "DolphinQt/QtUtils/ModalMessageBox.h"
  19. #include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
  20. #include "DolphinQt/QtUtils/UTF8CodePointCountValidator.h"
  21. #include "DolphinQt/Settings.h"
  22. #include "UICommon/GameFile.h"
  23. #include "UICommon/NetPlayIndex.h"
  24. NetPlaySetupDialog::NetPlaySetupDialog(const GameListModel& game_list_model, QWidget* parent)
  25. : QDialog(parent), m_game_list_model(game_list_model)
  26. {
  27. setWindowTitle(tr("NetPlay Setup"));
  28. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
  29. CreateMainLayout();
  30. bool use_index = Config::Get(Config::NETPLAY_USE_INDEX);
  31. std::string index_region = Config::Get(Config::NETPLAY_INDEX_REGION);
  32. std::string index_name = Config::Get(Config::NETPLAY_INDEX_NAME);
  33. std::string index_password = Config::Get(Config::NETPLAY_INDEX_PASSWORD);
  34. std::string nickname = Config::Get(Config::NETPLAY_NICKNAME);
  35. std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE);
  36. int connect_port = Config::Get(Config::NETPLAY_CONNECT_PORT);
  37. int host_port = Config::Get(Config::NETPLAY_HOST_PORT);
  38. int host_listen_port = Config::Get(Config::NETPLAY_LISTEN_PORT);
  39. bool enable_chunked_upload_limit = Config::Get(Config::NETPLAY_ENABLE_CHUNKED_UPLOAD_LIMIT);
  40. u32 chunked_upload_limit = Config::Get(Config::NETPLAY_CHUNKED_UPLOAD_LIMIT);
  41. #ifdef USE_UPNP
  42. bool use_upnp = Config::Get(Config::NETPLAY_USE_UPNP);
  43. m_host_upnp->setChecked(use_upnp);
  44. #endif
  45. m_nickname_edit->setText(QString::fromStdString(nickname));
  46. m_connection_type->setCurrentIndex(traversal_choice == "direct" ? 0 : 1);
  47. m_connect_port_box->setValue(connect_port);
  48. m_host_port_box->setValue(host_port);
  49. m_host_force_port_box->setValue(host_listen_port);
  50. m_host_force_port_box->setEnabled(false);
  51. m_host_server_browser->setChecked(use_index);
  52. m_host_server_region->setEnabled(use_index);
  53. m_host_server_region->setCurrentIndex(
  54. m_host_server_region->findData(QString::fromStdString(index_region)));
  55. m_host_server_name->setEnabled(use_index);
  56. m_host_server_name->setText(QString::fromStdString(index_name));
  57. m_host_server_password->setEnabled(use_index);
  58. m_host_server_password->setText(QString::fromStdString(index_password));
  59. m_host_chunked_upload_limit_check->setChecked(enable_chunked_upload_limit);
  60. m_host_chunked_upload_limit_box->setValue(chunked_upload_limit);
  61. m_host_chunked_upload_limit_box->setEnabled(enable_chunked_upload_limit);
  62. OnConnectionTypeChanged(m_connection_type->currentIndex());
  63. ConnectWidgets();
  64. }
  65. void NetPlaySetupDialog::CreateMainLayout()
  66. {
  67. m_main_layout = new QGridLayout;
  68. m_button_box = new QDialogButtonBox(QDialogButtonBox::Cancel);
  69. m_nickname_edit = new QLineEdit;
  70. m_connection_type = new QComboBox;
  71. m_reset_traversal_button = new NonDefaultQPushButton(tr("Reset Traversal Settings"));
  72. m_tab_widget = new QTabWidget;
  73. m_nickname_edit->setValidator(
  74. new UTF8CodePointCountValidator(NetPlay::MAX_NAME_LENGTH, m_nickname_edit));
  75. // Connection widget
  76. auto* connection_widget = new QWidget;
  77. auto* connection_layout = new QGridLayout;
  78. m_ip_label = new QLabel;
  79. m_ip_edit = new QLineEdit;
  80. m_connect_port_label = new QLabel(tr("Port:"));
  81. m_connect_port_box = new QSpinBox;
  82. m_connect_button = new NonDefaultQPushButton(tr("Connect"));
  83. m_connect_port_box->setMaximum(65535);
  84. connection_layout->addWidget(m_ip_label, 0, 0);
  85. connection_layout->addWidget(m_ip_edit, 0, 1);
  86. connection_layout->addWidget(m_connect_port_label, 0, 2);
  87. connection_layout->addWidget(m_connect_port_box, 0, 3);
  88. auto* const alert_label = new QLabel(
  89. tr("ALERT:\n\n"
  90. "All players must use the same Dolphin version.\n"
  91. "If enabled, SD cards must be identical between players.\n"
  92. "If DSP LLE is used, DSP ROMs must be identical between players.\n"
  93. "If a game is hanging on boot, it may not support Dual Core Netplay."
  94. " Disable Dual Core.\n"
  95. "If connecting directly, the host must have the chosen UDP port open/forwarded!\n"
  96. "\n"
  97. "Wii Remote support in netplay is experimental and may not work correctly.\n"
  98. "Use at your own risk.\n"));
  99. // Prevent the label from stretching vertically so the spacer gets all the extra space
  100. alert_label->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
  101. connection_layout->addWidget(alert_label, 1, 0, 1, -1);
  102. connection_layout->addItem(new QSpacerItem(1, 1), 2, 0, -1, -1);
  103. connection_layout->addWidget(m_connect_button, 3, 3, Qt::AlignRight);
  104. connection_widget->setLayout(connection_layout);
  105. // Host widget
  106. auto* host_widget = new QWidget;
  107. auto* host_layout = new QGridLayout;
  108. m_host_port_label = new QLabel(tr("Port:"));
  109. m_host_port_box = new QSpinBox;
  110. m_host_force_port_check = new QCheckBox(tr("Force Listen Port:"));
  111. m_host_force_port_box = new QSpinBox;
  112. m_host_chunked_upload_limit_check = new QCheckBox(tr("Limit Chunked Upload Speed:"));
  113. m_host_chunked_upload_limit_box = new QSpinBox;
  114. m_host_server_browser = new QCheckBox(tr("Show in server browser"));
  115. m_host_server_name = new QLineEdit;
  116. m_host_server_password = new QLineEdit;
  117. m_host_server_region = new QComboBox;
  118. #ifdef USE_UPNP
  119. m_host_upnp = new QCheckBox(tr("Forward port (UPnP)"));
  120. #endif
  121. m_host_games = new QListWidget;
  122. m_host_button = new NonDefaultQPushButton(tr("Host"));
  123. m_host_port_box->setMaximum(65535);
  124. m_host_force_port_box->setMaximum(65535);
  125. m_host_chunked_upload_limit_box->setRange(1, 1000000);
  126. m_host_chunked_upload_limit_box->setSingleStep(100);
  127. m_host_chunked_upload_limit_box->setSuffix(QStringLiteral(" kbps"));
  128. m_host_chunked_upload_limit_check->setToolTip(tr(
  129. "This will limit the speed of chunked uploading per client, which is used for save sync."));
  130. m_host_server_name->setToolTip(tr("Name of your session shown in the server browser"));
  131. m_host_server_name->setPlaceholderText(tr("Name"));
  132. m_host_server_password->setToolTip(tr("Password for joining your game (leave empty for none)"));
  133. m_host_server_password->setPlaceholderText(tr("Password"));
  134. for (const auto& region : NetPlayIndex::GetRegions())
  135. {
  136. m_host_server_region->addItem(
  137. tr("%1 (%2)").arg(tr(region.second.c_str())).arg(QString::fromStdString(region.first)),
  138. QString::fromStdString(region.first));
  139. }
  140. host_layout->addWidget(m_host_port_label, 0, 0);
  141. host_layout->addWidget(m_host_port_box, 0, 1);
  142. #ifdef USE_UPNP
  143. host_layout->addWidget(m_host_upnp, 0, 2);
  144. #endif
  145. host_layout->addWidget(m_host_server_browser, 1, 0);
  146. host_layout->addWidget(m_host_server_region, 1, 1);
  147. host_layout->addWidget(m_host_server_name, 1, 2);
  148. host_layout->addWidget(m_host_server_password, 1, 3);
  149. host_layout->addWidget(m_host_games, 2, 0, 1, -1);
  150. host_layout->addWidget(m_host_force_port_check, 3, 0);
  151. host_layout->addWidget(m_host_force_port_box, 3, 1, Qt::AlignLeft);
  152. host_layout->addWidget(m_host_chunked_upload_limit_check, 4, 0);
  153. host_layout->addWidget(m_host_chunked_upload_limit_box, 4, 1, Qt::AlignLeft);
  154. host_layout->addWidget(m_host_button, 4, 3, 2, 1, Qt::AlignRight);
  155. host_widget->setLayout(host_layout);
  156. m_connection_type->addItem(tr("Direct Connection"));
  157. m_connection_type->addItem(tr("Traversal Server"));
  158. m_main_layout->addWidget(new QLabel(tr("Connection Type:")), 0, 0);
  159. m_main_layout->addWidget(m_connection_type, 0, 1);
  160. m_main_layout->addWidget(m_reset_traversal_button, 0, 2);
  161. m_main_layout->addWidget(new QLabel(tr("Nickname:")), 1, 0);
  162. m_main_layout->addWidget(m_nickname_edit, 1, 1);
  163. m_main_layout->addWidget(m_tab_widget, 2, 0, 1, -1);
  164. m_main_layout->addWidget(m_button_box, 3, 0, 1, -1);
  165. // Tabs
  166. m_tab_widget->addTab(connection_widget, tr("Connect"));
  167. m_tab_widget->addTab(host_widget, tr("Host"));
  168. setLayout(m_main_layout);
  169. }
  170. void NetPlaySetupDialog::ConnectWidgets()
  171. {
  172. connect(m_connection_type, &QComboBox::currentIndexChanged, this,
  173. &NetPlaySetupDialog::OnConnectionTypeChanged);
  174. connect(m_nickname_edit, &QLineEdit::textChanged, this, &NetPlaySetupDialog::SaveSettings);
  175. // Connect widget
  176. connect(m_ip_edit, &QLineEdit::textChanged, this, &NetPlaySetupDialog::SaveSettings);
  177. connect(m_connect_port_box, &QSpinBox::valueChanged, this, &NetPlaySetupDialog::SaveSettings);
  178. // Host widget
  179. connect(m_host_port_box, &QSpinBox::valueChanged, this, &NetPlaySetupDialog::SaveSettings);
  180. connect(m_host_games, &QListWidget::currentRowChanged, [this](int index) {
  181. Settings::GetQSettings().setValue(QStringLiteral("netplay/hostgame"),
  182. m_host_games->item(index)->text());
  183. });
  184. connect(m_host_games, &QListWidget::itemDoubleClicked, this, &NetPlaySetupDialog::accept);
  185. connect(m_host_force_port_check, &QCheckBox::toggled,
  186. [this](bool value) { m_host_force_port_box->setEnabled(value); });
  187. connect(m_host_chunked_upload_limit_check, &QCheckBox::toggled, this, [this](bool value) {
  188. m_host_chunked_upload_limit_box->setEnabled(value);
  189. SaveSettings();
  190. });
  191. connect(m_host_chunked_upload_limit_box, &QSpinBox::valueChanged, this,
  192. &NetPlaySetupDialog::SaveSettings);
  193. connect(m_host_server_browser, &QCheckBox::toggled, this, &NetPlaySetupDialog::SaveSettings);
  194. connect(m_host_server_name, &QLineEdit::textChanged, this, &NetPlaySetupDialog::SaveSettings);
  195. connect(m_host_server_password, &QLineEdit::textChanged, this, &NetPlaySetupDialog::SaveSettings);
  196. connect(m_host_server_region,
  197. static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
  198. &NetPlaySetupDialog::SaveSettings);
  199. #ifdef USE_UPNP
  200. connect(m_host_upnp, &QCheckBox::stateChanged, this, &NetPlaySetupDialog::SaveSettings);
  201. #endif
  202. connect(m_connect_button, &QPushButton::clicked, this, &QDialog::accept);
  203. connect(m_host_button, &QPushButton::clicked, this, &QDialog::accept);
  204. connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
  205. connect(m_reset_traversal_button, &QPushButton::clicked, this,
  206. &NetPlaySetupDialog::ResetTraversalHost);
  207. connect(m_host_server_browser, &QCheckBox::toggled, this, [this](bool value) {
  208. m_host_server_region->setEnabled(value);
  209. m_host_server_name->setEnabled(value);
  210. m_host_server_password->setEnabled(value);
  211. });
  212. }
  213. void NetPlaySetupDialog::SaveSettings()
  214. {
  215. Config::ConfigChangeCallbackGuard config_guard;
  216. Config::SetBaseOrCurrent(Config::NETPLAY_NICKNAME, m_nickname_edit->text().toStdString());
  217. Config::SetBaseOrCurrent(m_connection_type->currentIndex() == 0 ? Config::NETPLAY_ADDRESS :
  218. Config::NETPLAY_HOST_CODE,
  219. m_ip_edit->text().toStdString());
  220. Config::SetBaseOrCurrent(Config::NETPLAY_CONNECT_PORT,
  221. static_cast<u16>(m_connect_port_box->value()));
  222. Config::SetBaseOrCurrent(Config::NETPLAY_HOST_PORT, static_cast<u16>(m_host_port_box->value()));
  223. #ifdef USE_UPNP
  224. Config::SetBaseOrCurrent(Config::NETPLAY_USE_UPNP, m_host_upnp->isChecked());
  225. #endif
  226. if (m_host_force_port_check->isChecked())
  227. Config::SetBaseOrCurrent(Config::NETPLAY_LISTEN_PORT,
  228. static_cast<u16>(m_host_force_port_box->value()));
  229. Config::SetBaseOrCurrent(Config::NETPLAY_ENABLE_CHUNKED_UPLOAD_LIMIT,
  230. m_host_chunked_upload_limit_check->isChecked());
  231. Config::SetBaseOrCurrent(Config::NETPLAY_CHUNKED_UPLOAD_LIMIT,
  232. m_host_chunked_upload_limit_box->value());
  233. Config::SetBaseOrCurrent(Config::NETPLAY_USE_INDEX, m_host_server_browser->isChecked());
  234. Config::SetBaseOrCurrent(Config::NETPLAY_INDEX_REGION,
  235. m_host_server_region->currentData().toString().toStdString());
  236. Config::SetBaseOrCurrent(Config::NETPLAY_INDEX_NAME, m_host_server_name->text().toStdString());
  237. Config::SetBaseOrCurrent(Config::NETPLAY_INDEX_PASSWORD,
  238. m_host_server_password->text().toStdString());
  239. }
  240. void NetPlaySetupDialog::OnConnectionTypeChanged(int index)
  241. {
  242. m_connect_port_box->setHidden(index != 0);
  243. m_connect_port_label->setHidden(index != 0);
  244. m_host_port_label->setHidden(index != 0);
  245. m_host_port_box->setHidden(index != 0);
  246. #ifdef USE_UPNP
  247. m_host_upnp->setHidden(index != 0);
  248. #endif
  249. m_host_force_port_check->setHidden(index == 0);
  250. m_host_force_port_box->setHidden(index == 0);
  251. m_reset_traversal_button->setHidden(index == 0);
  252. std::string address =
  253. index == 0 ? Config::Get(Config::NETPLAY_ADDRESS) : Config::Get(Config::NETPLAY_HOST_CODE);
  254. m_ip_label->setText(index == 0 ? tr("IP Address:") : tr("Host Code:"));
  255. m_ip_edit->setText(QString::fromStdString(address));
  256. Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE,
  257. std::string(index == 0 ? "direct" : "traversal"));
  258. }
  259. void NetPlaySetupDialog::show()
  260. {
  261. PopulateGameList();
  262. QDialog::show();
  263. }
  264. void NetPlaySetupDialog::accept()
  265. {
  266. SaveSettings();
  267. if (m_tab_widget->currentIndex() == 0)
  268. {
  269. emit Join();
  270. }
  271. else
  272. {
  273. auto items = m_host_games->selectedItems();
  274. if (items.empty())
  275. {
  276. ModalMessageBox::critical(this, tr("Error"), tr("You must select a game to host!"));
  277. return;
  278. }
  279. if (m_host_server_browser->isChecked() && m_host_server_name->text().isEmpty())
  280. {
  281. ModalMessageBox::critical(this, tr("Error"), tr("You must provide a name for your session!"));
  282. return;
  283. }
  284. if (m_host_server_browser->isChecked() &&
  285. m_host_server_region->currentData().toString().isEmpty())
  286. {
  287. ModalMessageBox::critical(this, tr("Error"),
  288. tr("You must provide a region for your session!"));
  289. return;
  290. }
  291. emit Host(*items[0]->data(Qt::UserRole).value<std::shared_ptr<const UICommon::GameFile>>());
  292. }
  293. }
  294. void NetPlaySetupDialog::PopulateGameList()
  295. {
  296. QSignalBlocker blocker(m_host_games);
  297. m_host_games->clear();
  298. for (int i = 0; i < m_game_list_model.rowCount(QModelIndex()); i++)
  299. {
  300. std::shared_ptr<const UICommon::GameFile> game = m_game_list_model.GetGameFile(i);
  301. auto* item =
  302. new QListWidgetItem(QString::fromStdString(m_game_list_model.GetNetPlayName(*game)));
  303. item->setData(Qt::UserRole, QVariant::fromValue(std::move(game)));
  304. m_host_games->addItem(item);
  305. }
  306. m_host_games->sortItems();
  307. const QString selected_game =
  308. Settings::GetQSettings().value(QStringLiteral("netplay/hostgame"), QString{}).toString();
  309. const auto find_list = m_host_games->findItems(selected_game, Qt::MatchFlag::MatchExactly);
  310. if (find_list.count() > 0)
  311. m_host_games->setCurrentItem(find_list[0]);
  312. }
  313. void NetPlaySetupDialog::ResetTraversalHost()
  314. {
  315. Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_SERVER,
  316. Config::NETPLAY_TRAVERSAL_SERVER.GetDefaultValue());
  317. Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_PORT,
  318. Config::NETPLAY_TRAVERSAL_PORT.GetDefaultValue());
  319. ModalMessageBox::information(
  320. this, tr("Reset Traversal Server"),
  321. tr("Reset Traversal Server to %1:%2")
  322. .arg(QString::fromStdString(Config::NETPLAY_TRAVERSAL_SERVER.GetDefaultValue()),
  323. QString::number(Config::NETPLAY_TRAVERSAL_PORT.GetDefaultValue())));
  324. }