diff options
author | Zephyron <zephyron@citron-emu.org> | 2024-12-31 16:19:25 +1000 |
---|---|---|
committer | Zephyron <zephyron@citron-emu.org> | 2024-12-31 16:19:25 +1000 |
commit | 9427e27e24a7135880ee2881c3c44988e174b41a (patch) | |
tree | 83f0062a35be144f6b162eaa823c5b3c7620146e /src/yuzu/multiplayer | |
parent | b35ae725d20960411e8588b11c12a2d55f86c9d0 (diff) |
chore: update project branding to citron
Diffstat (limited to 'src/yuzu/multiplayer')
24 files changed, 0 insertions, 3664 deletions
diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp deleted file mode 100644 index 4463616b4..000000000 --- a/src/yuzu/multiplayer/chat_room.cpp +++ /dev/null @@ -1,508 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <array> -#include <future> -#include <QColor> -#include <QDesktopServices> -#include <QFutureWatcher> -#include <QImage> -#include <QList> -#include <QLocale> -#include <QMenu> -#include <QMessageBox> -#include <QMetaType> -#include <QTime> -#include <QUrl> -#include <QtConcurrent/QtConcurrentRun> -#include "common/logging/log.h" -#include "network/announce_multiplayer_session.h" -#include "ui_chat_room.h" -#include "yuzu/game_list_p.h" -#include "yuzu/multiplayer/chat_room.h" -#include "yuzu/multiplayer/message.h" -#ifdef ENABLE_WEB_SERVICE -#include "web_service/web_backend.h" -#endif - -class ChatMessage { -public: - explicit ChatMessage(const Network::ChatEntry& chat, Network::RoomNetwork& room_network, - QTime ts = {}) { - /// Convert the time to their default locale defined format - QLocale locale; - timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat); - nickname = QString::fromStdString(chat.nickname); - username = QString::fromStdString(chat.username); - message = QString::fromStdString(chat.message); - - // Check for user pings - QString cur_nickname, cur_username; - if (auto room = room_network.GetRoomMember().lock()) { - cur_nickname = QString::fromStdString(room->GetNickname()); - cur_username = QString::fromStdString(room->GetUsername()); - } - - // Handle pings at the beginning and end of message - QString fixed_message = QStringLiteral(" %1 ").arg(message); - if (fixed_message.contains(QStringLiteral(" @%1 ").arg(cur_nickname)) || - (!cur_username.isEmpty() && - fixed_message.contains(QStringLiteral(" @%1 ").arg(cur_username)))) { - - contains_ping = true; - } else { - contains_ping = false; - } - } - - bool ContainsPing() const { - return contains_ping; - } - - /// Format the message using the players color - QString GetPlayerChatMessage(u16 player) const { - const bool is_dark_theme = QIcon::themeName().contains(QStringLiteral("dark")) || - QIcon::themeName().contains(QStringLiteral("midnight")); - auto color = - is_dark_theme ? player_color_dark[player % 16] : player_color_default[player % 16]; - QString name; - if (username.isEmpty() || username == nickname) { - name = nickname; - } else { - name = QStringLiteral("%1 (%2)").arg(nickname, username); - } - - QString style, text_color; - if (ContainsPing()) { - // Add a background color to these messages - style = QStringLiteral("background-color: %1").arg(QString::fromStdString(ping_color)); - // Add a font color - text_color = QStringLiteral("color='#000000'"); - } - - return QStringLiteral("[%1] <font color='%2'><%3></font> <font style='%4' " - "%5>%6</font>") - .arg(timestamp, QString::fromStdString(color), name.toHtmlEscaped(), style, text_color, - message.toHtmlEscaped()); - } - -private: - static constexpr std::array<const char*, 16> player_color_default = { - {"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222", - "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "#FFFF00"}}; - static constexpr std::array<const char*, 16> player_color_dark = { - {"#559AD1", "#4EC9A8", "#D69D85", "#C6C923", "#B975B5", "#D81F1F", "#7EAE39", "#4F8733", - "#F7CD8A", "#6FCACF", "#CE4897", "#8A2BE2", "#D2691E", "#9ACD32", "#FF7F50", "#152ccd"}}; - static constexpr char ping_color[] = "#FFFF00"; - - QString timestamp; - QString nickname; - QString username; - QString message; - bool contains_ping; -}; - -class StatusMessage { -public: - explicit StatusMessage(const QString& msg, QTime ts = {}) { - /// Convert the time to their default locale defined format - QLocale locale; - timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat); - message = msg; - } - - QString GetSystemChatMessage() const { - return QStringLiteral("[%1] <font color='%2'>* %3</font>") - .arg(timestamp, QString::fromStdString(system_color), message); - } - -private: - static constexpr const char system_color[] = "#FF8C00"; - QString timestamp; - QString message; -}; - -class PlayerListItem : public QStandardItem { -public: - static const int NicknameRole = Qt::UserRole + 1; - static const int UsernameRole = Qt::UserRole + 2; - static const int AvatarUrlRole = Qt::UserRole + 3; - static const int GameNameRole = Qt::UserRole + 4; - static const int GameVersionRole = Qt::UserRole + 5; - - PlayerListItem() = default; - explicit PlayerListItem(const std::string& nickname, const std::string& username, - const std::string& avatar_url, - const AnnounceMultiplayerRoom::GameInfo& game_info) { - setEditable(false); - setData(QString::fromStdString(nickname), NicknameRole); - setData(QString::fromStdString(username), UsernameRole); - setData(QString::fromStdString(avatar_url), AvatarUrlRole); - if (game_info.name.empty()) { - setData(QObject::tr("Not playing a game"), GameNameRole); - } else { - setData(QString::fromStdString(game_info.name), GameNameRole); - } - setData(QString::fromStdString(game_info.version), GameVersionRole); - } - - QVariant data(int role) const override { - if (role != Qt::DisplayRole) { - return QStandardItem::data(role); - } - QString name; - const QString nickname = data(NicknameRole).toString(); - const QString username = data(UsernameRole).toString(); - if (username.isEmpty() || username == nickname) { - name = nickname; - } else { - name = QStringLiteral("%1 (%2)").arg(nickname, username); - } - const QString version = data(GameVersionRole).toString(); - QString version_string; - if (!version.isEmpty()) { - version_string = QStringLiteral("(%1)").arg(version); - } - return QStringLiteral("%1\n %2 %3") - .arg(name, data(GameNameRole).toString(), version_string); - } -}; - -ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ChatRoom>()) { - ui->setupUi(this); - - // set the item_model for player_view - - player_list = new QStandardItemModel(ui->player_view); - ui->player_view->setModel(player_list); - ui->player_view->setContextMenuPolicy(Qt::CustomContextMenu); - // set a header to make it look better though there is only one column - player_list->insertColumns(0, 1); - player_list->setHeaderData(0, Qt::Horizontal, tr("Members")); - - ui->chat_history->document()->setMaximumBlockCount(max_chat_lines); - - auto font = ui->chat_history->font(); - font.setPointSizeF(10); - ui->chat_history->setFont(font); - - // register the network structs to use in slots and signals - qRegisterMetaType<Network::ChatEntry>(); - qRegisterMetaType<Network::StatusMessageEntry>(); - qRegisterMetaType<Network::RoomInformation>(); - qRegisterMetaType<Network::RoomMember::State>(); - - // Connect all the widgets to the appropriate events - connect(ui->player_view, &QTreeView::customContextMenuRequested, this, - &ChatRoom::PopupContextMenu); - connect(ui->chat_message, &QLineEdit::returnPressed, this, &ChatRoom::OnSendChat); - connect(ui->chat_message, &QLineEdit::textChanged, this, &ChatRoom::OnChatTextChanged); - connect(ui->send_message, &QPushButton::clicked, this, &ChatRoom::OnSendChat); -} - -ChatRoom::~ChatRoom() = default; - -void ChatRoom::Initialize(Network::RoomNetwork* room_network_) { - room_network = room_network_; - // setup the callbacks for network updates - if (auto member = room_network->GetRoomMember().lock()) { - member->BindOnChatMessageReceived( - [this](const Network::ChatEntry& chat) { emit ChatReceived(chat); }); - member->BindOnStatusMessageReceived( - [this](const Network::StatusMessageEntry& status_message) { - emit StatusMessageReceived(status_message); - }); - connect(this, &ChatRoom::ChatReceived, this, &ChatRoom::OnChatReceive); - connect(this, &ChatRoom::StatusMessageReceived, this, &ChatRoom::OnStatusMessageReceive); - } -} - -void ChatRoom::SetModPerms(bool is_mod) { - has_mod_perms = is_mod; -} - -void ChatRoom::RetranslateUi() { - ui->retranslateUi(this); -} - -void ChatRoom::Clear() { - ui->chat_history->clear(); - block_list.clear(); -} - -void ChatRoom::AppendStatusMessage(const QString& msg) { - ui->chat_history->append(StatusMessage(msg).GetSystemChatMessage()); -} - -void ChatRoom::AppendChatMessage(const QString& msg) { - ui->chat_history->append(msg); -} - -void ChatRoom::SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname) { - if (auto room = room_network->GetRoomMember().lock()) { - auto members = room->GetMemberInformation(); - auto it = std::find_if(members.begin(), members.end(), - [&nickname](const Network::RoomMember::MemberInformation& member) { - return member.nickname == nickname; - }); - if (it == members.end()) { - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::NO_SUCH_USER); - return; - } - room->SendModerationRequest(type, nickname); - } -} - -bool ChatRoom::ValidateMessage(const std::string& msg) { - return !msg.empty(); -} - -void ChatRoom::OnRoomUpdate(const Network::RoomInformation& info) { - // TODO(B3N30): change title - if (auto room_member = room_network->GetRoomMember().lock()) { - SetPlayerList(room_member->GetMemberInformation()); - } -} - -void ChatRoom::Disable() { - ui->send_message->setDisabled(true); - ui->chat_message->setDisabled(true); -} - -void ChatRoom::Enable() { - ui->send_message->setEnabled(true); - ui->chat_message->setEnabled(true); -} - -void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) { - if (!ValidateMessage(chat.message)) { - return; - } - if (auto room = room_network->GetRoomMember().lock()) { - // get the id of the player - auto members = room->GetMemberInformation(); - auto it = std::find_if(members.begin(), members.end(), - [&chat](const Network::RoomMember::MemberInformation& member) { - return member.nickname == chat.nickname && - member.username == chat.username; - }); - if (it == members.end()) { - LOG_INFO(Network, "Chat message received from unknown player. Ignoring it."); - return; - } - if (block_list.count(chat.nickname)) { - LOG_INFO(Network, "Chat message received from blocked player {}. Ignoring it.", - chat.nickname); - return; - } - auto player = std::distance(members.begin(), it); - ChatMessage m(chat, *room_network); - if (m.ContainsPing()) { - emit UserPinged(); - } - AppendChatMessage(m.GetPlayerChatMessage(player)); - } -} - -void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_message) { - QString name; - if (status_message.username.empty() || status_message.username == status_message.nickname) { - name = QString::fromStdString(status_message.nickname); - } else { - name = QStringLiteral("%1 (%2)").arg(QString::fromStdString(status_message.nickname), - QString::fromStdString(status_message.username)); - } - QString message; - switch (status_message.type) { - case Network::IdMemberJoin: - message = tr("%1 has joined").arg(name); - break; - case Network::IdMemberLeave: - message = tr("%1 has left").arg(name); - break; - case Network::IdMemberKicked: - message = tr("%1 has been kicked").arg(name); - break; - case Network::IdMemberBanned: - message = tr("%1 has been banned").arg(name); - break; - case Network::IdAddressUnbanned: - message = tr("%1 has been unbanned").arg(name); - break; - } - if (!message.isEmpty()) - AppendStatusMessage(message); -} - -void ChatRoom::OnSendChat() { - if (auto room_member = room_network->GetRoomMember().lock()) { - if (!room_member->IsConnected()) { - return; - } - auto message = ui->chat_message->text().toStdString(); - if (!ValidateMessage(message)) { - return; - } - auto nick = room_member->GetNickname(); - auto username = room_member->GetUsername(); - Network::ChatEntry chat{nick, username, message}; - - auto members = room_member->GetMemberInformation(); - auto it = std::find_if(members.begin(), members.end(), - [&chat](const Network::RoomMember::MemberInformation& member) { - return member.nickname == chat.nickname && - member.username == chat.username; - }); - if (it == members.end()) { - LOG_INFO(Network, "Cannot find self in the player list when sending a message."); - } - auto player = std::distance(members.begin(), it); - ChatMessage m(chat, *room_network); - room_member->SendChatMessage(message); - AppendChatMessage(m.GetPlayerChatMessage(player)); - ui->chat_message->clear(); - } -} - -void ChatRoom::UpdateIconDisplay() { - for (int row = 0; row < player_list->invisibleRootItem()->rowCount(); ++row) { - QStandardItem* item = player_list->invisibleRootItem()->child(row); - const std::string avatar_url = - item->data(PlayerListItem::AvatarUrlRole).toString().toStdString(); - if (icon_cache.count(avatar_url)) { - item->setData(icon_cache.at(avatar_url), Qt::DecorationRole); - } else { - item->setData(QIcon::fromTheme(QStringLiteral("no_avatar")).pixmap(48), - Qt::DecorationRole); - } - } -} - -void ChatRoom::SetPlayerList(const Network::RoomMember::MemberList& member_list) { - // TODO(B3N30): Remember which row is selected - player_list->removeRows(0, player_list->rowCount()); - for (const auto& member : member_list) { - if (member.nickname.empty()) - continue; - QStandardItem* name_item = new PlayerListItem(member.nickname, member.username, - member.avatar_url, member.game_info); - -#ifdef ENABLE_WEB_SERVICE - if (!icon_cache.count(member.avatar_url) && !member.avatar_url.empty()) { - // Start a request to get the member's avatar - const QUrl url(QString::fromStdString(member.avatar_url)); - QFuture<std::string> future = QtConcurrent::run([url] { - WebService::Client client( - QStringLiteral("%1://%2").arg(url.scheme(), url.host()).toStdString(), "", ""); - auto result = client.GetImage(url.path().toStdString(), true); - if (result.returned_data.empty()) { - LOG_ERROR(WebService, "Failed to get avatar"); - } - return result.returned_data; - }); - auto* future_watcher = new QFutureWatcher<std::string>(this); - connect(future_watcher, &QFutureWatcher<std::string>::finished, this, - [this, future_watcher, avatar_url = member.avatar_url] { - const std::string result = future_watcher->result(); - if (result.empty()) - return; - QPixmap pixmap; - if (!pixmap.loadFromData(reinterpret_cast<const u8*>(result.data()), - static_cast<uint>(result.size()))) - return; - icon_cache[avatar_url] = - pixmap.scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - // Update all the displayed icons with the new icon_cache - UpdateIconDisplay(); - }); - future_watcher->setFuture(future); - } -#endif - - player_list->invisibleRootItem()->appendRow(name_item); - } - UpdateIconDisplay(); - // TODO(B3N30): Restore row selection -} - -void ChatRoom::OnChatTextChanged() { - if (ui->chat_message->text().length() > static_cast<int>(Network::MaxMessageSize)) - ui->chat_message->setText( - ui->chat_message->text().left(static_cast<int>(Network::MaxMessageSize))); -} - -void ChatRoom::PopupContextMenu(const QPoint& menu_location) { - QModelIndex item = ui->player_view->indexAt(menu_location); - if (!item.isValid()) - return; - - std::string nickname = - player_list->item(item.row())->data(PlayerListItem::NicknameRole).toString().toStdString(); - - QMenu context_menu; - - QString username = player_list->item(item.row())->data(PlayerListItem::UsernameRole).toString(); - if (!username.isEmpty()) { - QAction* view_profile_action = context_menu.addAction(tr("View Profile")); - connect(view_profile_action, &QAction::triggered, [username] { - QDesktopServices::openUrl( - QUrl(QStringLiteral("https://community.citra-emu.org/u/%1").arg(username))); - }); - } - - std::string cur_nickname; - if (auto room = room_network->GetRoomMember().lock()) { - cur_nickname = room->GetNickname(); - } - - if (nickname != cur_nickname) { // You can't block yourself - QAction* block_action = context_menu.addAction(tr("Block Player")); - - block_action->setCheckable(true); - block_action->setChecked(block_list.count(nickname) > 0); - - connect(block_action, &QAction::triggered, [this, nickname] { - if (block_list.count(nickname)) { - block_list.erase(nickname); - } else { - QMessageBox::StandardButton result = QMessageBox::question( - this, tr("Block Player"), - tr("When you block a player, you will no longer receive chat messages from " - "them.<br><br>Are you sure you would like to block %1?") - .arg(QString::fromStdString(nickname)), - QMessageBox::Yes | QMessageBox::No); - if (result == QMessageBox::Yes) - block_list.emplace(nickname); - } - }); - } - - if (has_mod_perms && nickname != cur_nickname) { // You can't kick or ban yourself - context_menu.addSeparator(); - - QAction* kick_action = context_menu.addAction(tr("Kick")); - QAction* ban_action = context_menu.addAction(tr("Ban")); - - connect(kick_action, &QAction::triggered, [this, nickname] { - QMessageBox::StandardButton result = - QMessageBox::question(this, tr("Kick Player"), - tr("Are you sure you would like to <b>kick</b> %1?") - .arg(QString::fromStdString(nickname)), - QMessageBox::Yes | QMessageBox::No); - if (result == QMessageBox::Yes) - SendModerationRequest(Network::IdModKick, nickname); - }); - connect(ban_action, &QAction::triggered, [this, nickname] { - QMessageBox::StandardButton result = QMessageBox::question( - this, tr("Ban Player"), - tr("Are you sure you would like to <b>kick and ban</b> %1?\n\nThis would " - "ban both their forum username and their IP address.") - .arg(QString::fromStdString(nickname)), - QMessageBox::Yes | QMessageBox::No); - if (result == QMessageBox::Yes) - SendModerationRequest(Network::IdModBan, nickname); - }); - } - - context_menu.exec(ui->player_view->viewport()->mapToGlobal(menu_location)); -} diff --git a/src/yuzu/multiplayer/chat_room.h b/src/yuzu/multiplayer/chat_room.h deleted file mode 100644 index dd71ea4cd..000000000 --- a/src/yuzu/multiplayer/chat_room.h +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <memory> -#include <unordered_map> -#include <unordered_set> -#include <QDialog> -#include <QSortFilterProxyModel> -#include <QStandardItemModel> -#include <QVariant> -#include "network/network.h" - -namespace Ui { -class ChatRoom; -} - -namespace Core { -class AnnounceMultiplayerSession; -} - -class ConnectionError; -class ComboBoxProxyModel; - -class ChatMessage; - -class ChatRoom : public QWidget { - Q_OBJECT - -public: - explicit ChatRoom(QWidget* parent); - void Initialize(Network::RoomNetwork* room_network); - void RetranslateUi(); - void SetPlayerList(const Network::RoomMember::MemberList& member_list); - void Clear(); - void AppendStatusMessage(const QString& msg); - ~ChatRoom(); - - void SetModPerms(bool is_mod); - void UpdateIconDisplay(); - -public slots: - void OnRoomUpdate(const Network::RoomInformation& info); - void OnChatReceive(const Network::ChatEntry&); - void OnStatusMessageReceive(const Network::StatusMessageEntry&); - void OnSendChat(); - void OnChatTextChanged(); - void PopupContextMenu(const QPoint& menu_location); - void Disable(); - void Enable(); - -signals: - void ChatReceived(const Network::ChatEntry&); - void StatusMessageReceived(const Network::StatusMessageEntry&); - void UserPinged(); - -private: - static constexpr u32 max_chat_lines = 1000; - void AppendChatMessage(const QString&); - bool ValidateMessage(const std::string&); - void SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname); - - bool has_mod_perms = false; - QStandardItemModel* player_list; - std::unique_ptr<Ui::ChatRoom> ui; - std::unordered_set<std::string> block_list; - std::unordered_map<std::string, QPixmap> icon_cache; - Network::RoomNetwork* room_network; -}; - -Q_DECLARE_METATYPE(Network::ChatEntry); -Q_DECLARE_METATYPE(Network::StatusMessageEntry); -Q_DECLARE_METATYPE(Network::RoomInformation); -Q_DECLARE_METATYPE(Network::RoomMember::State); -Q_DECLARE_METATYPE(Network::RoomMember::Error); diff --git a/src/yuzu/multiplayer/chat_room.ui b/src/yuzu/multiplayer/chat_room.ui deleted file mode 100644 index f2b31b5da..000000000 --- a/src/yuzu/multiplayer/chat_room.ui +++ /dev/null @@ -1,59 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ChatRoom</class> - <widget class="QWidget" name="ChatRoom"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>807</width> - <height>432</height> - </rect> - </property> - <property name="windowTitle"> - <string>Room Window</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QTreeView" name="player_view"/> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="QTextEdit" name="chat_history"> - <property name="undoRedoEnabled"> - <bool>false</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - <property name="textInteractionFlags"> - <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QLineEdit" name="chat_message"> - <property name="placeholderText"> - <string>Send Chat Message</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="send_message"> - <property name="text"> - <string>Send Message</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp deleted file mode 100644 index caf34a414..000000000 --- a/src/yuzu/multiplayer/client_room.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <future> -#include <QColor> -#include <QImage> -#include <QList> -#include <QLocale> -#include <QMetaType> -#include <QTime> -#include <QtConcurrent/QtConcurrentRun> -#include "common/logging/log.h" -#include "network/announce_multiplayer_session.h" -#include "ui_client_room.h" -#include "yuzu/game_list_p.h" -#include "yuzu/multiplayer/client_room.h" -#include "yuzu/multiplayer/message.h" -#include "yuzu/multiplayer/moderation_dialog.h" -#include "yuzu/multiplayer/state.h" - -ClientRoomWindow::ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_) - : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui(std::make_unique<Ui::ClientRoom>()), room_network{room_network_} { - ui->setupUi(this); - ui->chat->Initialize(&room_network); - - // setup the callbacks for network updates - if (auto member = room_network.GetRoomMember().lock()) { - member->BindOnRoomInformationChanged( - [this](const Network::RoomInformation& info) { emit RoomInformationChanged(info); }); - member->BindOnStateChanged( - [this](const Network::RoomMember::State& state) { emit StateChanged(state); }); - - connect(this, &ClientRoomWindow::RoomInformationChanged, this, - &ClientRoomWindow::OnRoomUpdate); - connect(this, &ClientRoomWindow::StateChanged, this, &::ClientRoomWindow::OnStateChange); - // Update the state - OnStateChange(member->GetState()); - } else { - // TODO (jroweboy) network was not initialized? - } - - connect(ui->disconnect, &QPushButton::clicked, this, &ClientRoomWindow::Disconnect); - ui->disconnect->setDefault(false); - ui->disconnect->setAutoDefault(false); - connect(ui->moderation, &QPushButton::clicked, [this] { - ModerationDialog dialog(room_network, this); - dialog.exec(); - }); - ui->moderation->setDefault(false); - ui->moderation->setAutoDefault(false); - connect(ui->chat, &ChatRoom::UserPinged, this, &ClientRoomWindow::ShowNotification); - UpdateView(); -} - -ClientRoomWindow::~ClientRoomWindow() = default; - -void ClientRoomWindow::SetModPerms(bool is_mod) { - ui->chat->SetModPerms(is_mod); - ui->moderation->setVisible(is_mod); - ui->moderation->setDefault(false); - ui->moderation->setAutoDefault(false); -} - -void ClientRoomWindow::RetranslateUi() { - ui->retranslateUi(this); - ui->chat->RetranslateUi(); -} - -void ClientRoomWindow::OnRoomUpdate(const Network::RoomInformation& info) { - UpdateView(); -} - -void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) { - if (state == Network::RoomMember::State::Joined || - state == Network::RoomMember::State::Moderator) { - ui->chat->Clear(); - ui->chat->AppendStatusMessage(tr("Connected")); - SetModPerms(state == Network::RoomMember::State::Moderator); - } - UpdateView(); -} - -void ClientRoomWindow::Disconnect() { - auto parent = static_cast<MultiplayerState*>(parentWidget()); - if (parent->OnCloseRoom()) { - ui->chat->AppendStatusMessage(tr("Disconnected")); - close(); - } -} - -void ClientRoomWindow::UpdateView() { - if (auto member = room_network.GetRoomMember().lock()) { - if (member->IsConnected()) { - ui->chat->Enable(); - ui->disconnect->setEnabled(true); - auto memberlist = member->GetMemberInformation(); - ui->chat->SetPlayerList(memberlist); - const auto information = member->GetRoomInformation(); - setWindowTitle(QString(tr("%1 - %2 (%3/%4 members) - connected")) - .arg(QString::fromStdString(information.name)) - .arg(QString::fromStdString(information.preferred_game.name)) - .arg(memberlist.size()) - .arg(information.member_slots)); - ui->description->setText(QString::fromStdString(information.description)); - return; - } - } - // TODO(B3N30): can't get RoomMember*, show error and close window - close(); -} - -void ClientRoomWindow::UpdateIconDisplay() { - ui->chat->UpdateIconDisplay(); -} diff --git a/src/yuzu/multiplayer/client_room.h b/src/yuzu/multiplayer/client_room.h deleted file mode 100644 index f338e3c59..000000000 --- a/src/yuzu/multiplayer/client_room.h +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "yuzu/multiplayer/chat_room.h" - -namespace Ui { -class ClientRoom; -} - -class ClientRoomWindow : public QDialog { - Q_OBJECT - -public: - explicit ClientRoomWindow(QWidget* parent, Network::RoomNetwork& room_network_); - ~ClientRoomWindow(); - - void RetranslateUi(); - void UpdateIconDisplay(); - -public slots: - void OnRoomUpdate(const Network::RoomInformation&); - void OnStateChange(const Network::RoomMember::State&); - -signals: - void RoomInformationChanged(const Network::RoomInformation&); - void StateChanged(const Network::RoomMember::State&); - void ShowNotification(); - -private: - void Disconnect(); - void UpdateView(); - void SetModPerms(bool is_mod); - - QStandardItemModel* player_list; - std::unique_ptr<Ui::ClientRoom> ui; - Network::RoomNetwork& room_network; -}; diff --git a/src/yuzu/multiplayer/client_room.ui b/src/yuzu/multiplayer/client_room.ui deleted file mode 100644 index 97e88b502..000000000 --- a/src/yuzu/multiplayer/client_room.ui +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ClientRoom</class> - <widget class="QWidget" name="ClientRoom"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>807</width> - <height>432</height> - </rect> - </property> - <property name="windowTitle"> - <string>Room Window</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="rightMargin"> - <number>0</number> - </property> - <item> - <widget class="QLabel" name="description"> - <property name="text"> - <string>Room Description</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="moderation"> - <property name="text"> - <string>Moderation...</string> - </property> - <property name="visible"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="disconnect"> - <property name="text"> - <string>Leave Room</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="ChatRoom" name="chat" native="true"/> - </item> - </layout> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>ChatRoom</class> - <extends>QWidget</extends> - <header>multiplayer/chat_room.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp deleted file mode 100644 index 74da97e21..000000000 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <QComboBox> -#include <QFuture> -#include <QIntValidator> -#include <QRegularExpressionValidator> -#include <QString> -#include <QtConcurrent/QtConcurrentRun> -#include "common/settings.h" -#include "core/core.h" -#include "core/internal_network/network_interface.h" -#include "network/network.h" -#include "ui_direct_connect.h" -#include "yuzu/main.h" -#include "yuzu/multiplayer/client_room.h" -#include "yuzu/multiplayer/direct_connect.h" -#include "yuzu/multiplayer/message.h" -#include "yuzu/multiplayer/state.h" -#include "yuzu/multiplayer/validation.h" -#include "yuzu/uisettings.h" - -enum class ConnectionType : u8 { TraversalServer, IP }; - -DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent) - : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui(std::make_unique<Ui::DirectConnect>()), system{system_}, room_network{ - system.GetRoomNetwork()} { - - ui->setupUi(this); - - // setup the watcher for background connections - watcher = new QFutureWatcher<void>; - connect(watcher, &QFutureWatcher<void>::finished, this, &DirectConnectWindow::OnConnection); - - ui->nickname->setValidator(validation.GetNickname()); - ui->nickname->setText( - QString::fromStdString(UISettings::values.multiplayer_nickname.GetValue())); - if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) { - // Use yuzu Web Service user name as nickname by default - ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue())); - } - ui->ip->setValidator(validation.GetIP()); - ui->ip->setText(QString::fromStdString(UISettings::values.multiplayer_ip.GetValue())); - ui->port->setValidator(validation.GetPort()); - ui->port->setText(QString::number(UISettings::values.multiplayer_port.GetValue())); - - // TODO(jroweboy): Show or hide the connection options based on the current value of the combo - // box. Add this back in when the traversal server support is added. - connect(ui->connect, &QPushButton::clicked, this, &DirectConnectWindow::Connect); -} - -DirectConnectWindow::~DirectConnectWindow() = default; - -void DirectConnectWindow::RetranslateUi() { - ui->retranslateUi(this); -} - -void DirectConnectWindow::Connect() { - if (!Network::GetSelectedNetworkInterface()) { - NetworkMessage::ErrorManager::ShowError( - NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); - return; - } - if (!ui->nickname->hasAcceptableInput()) { - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID); - return; - } - if (system.IsPoweredOn()) { - if (!NetworkMessage::WarnGameRunning()) { - return; - } - } - if (const auto member = room_network.GetRoomMember().lock()) { - // Prevent the user from trying to join a room while they are already joining. - if (member->GetState() == Network::RoomMember::State::Joining) { - return; - } else if (member->IsConnected()) { - // And ask if they want to leave the room if they are already in one. - if (!NetworkMessage::WarnDisconnect()) { - return; - } - } - } - if (!ui->ip->hasAcceptableInput()) { - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID); - return; - } - if (!ui->port->hasAcceptableInput()) { - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID); - return; - } - - // Store settings - UISettings::values.multiplayer_nickname = ui->nickname->text().toStdString(); - UISettings::values.multiplayer_ip = ui->ip->text().toStdString(); - if (!ui->port->text().isEmpty()) { - UISettings::values.multiplayer_port = ui->port->text().toInt(); - } else { - UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault(); - } - - emit SaveConfig(); - - // attempt to connect in a different thread - QFuture<void> f = QtConcurrent::run([&] { - if (auto room_member = room_network.GetRoomMember().lock()) { - auto port = UISettings::values.multiplayer_port.GetValue(); - room_member->Join(ui->nickname->text().toStdString(), - ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredIP, - ui->password->text().toStdString().c_str()); - } - }); - watcher->setFuture(f); - // and disable widgets and display a connecting while we wait - BeginConnecting(); -} - -void DirectConnectWindow::BeginConnecting() { - ui->connect->setEnabled(false); - ui->connect->setText(tr("Connecting")); -} - -void DirectConnectWindow::EndConnecting() { - ui->connect->setEnabled(true); - ui->connect->setText(tr("Connect")); -} - -void DirectConnectWindow::OnConnection() { - EndConnecting(); - - if (auto room_member = room_network.GetRoomMember().lock()) { - if (room_member->IsConnected()) { - close(); - } - } -} diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h deleted file mode 100644 index b8f66cfb2..000000000 --- a/src/yuzu/multiplayer/direct_connect.h +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <memory> -#include <QDialog> -#include <QFutureWatcher> -#include "yuzu/multiplayer/validation.h" - -namespace Ui { -class DirectConnect; -} - -namespace Core { -class System; -} - -class DirectConnectWindow : public QDialog { - Q_OBJECT - -public: - explicit DirectConnectWindow(Core::System& system_, QWidget* parent = nullptr); - ~DirectConnectWindow(); - - void RetranslateUi(); - -signals: - /** - * Signalled by this widget when it is closing itself and destroying any state such as - * connections that it might have. - */ - void Closed(); - void SaveConfig(); - -private slots: - void OnConnection(); - -private: - void Connect(); - void BeginConnecting(); - void EndConnecting(); - - QFutureWatcher<void>* watcher; - std::unique_ptr<Ui::DirectConnect> ui; - Validation validation; - Core::System& system; - Network::RoomNetwork& room_network; -}; diff --git a/src/yuzu/multiplayer/direct_connect.ui b/src/yuzu/multiplayer/direct_connect.ui deleted file mode 100644 index 0dd4e6829..000000000 --- a/src/yuzu/multiplayer/direct_connect.ui +++ /dev/null @@ -1,165 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>DirectConnect</class> - <widget class="QWidget" name="DirectConnect"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>455</width> - <height>161</height> - </rect> - </property> - <property name="windowTitle"> - <string>Direct Connect</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="spacing"> - <number>0</number> - </property> - <property name="leftMargin"> - <number>0</number> - </property> - <item> - <widget class="QWidget" name="ip_container" native="true"> - <layout class="QHBoxLayout" name="ip_layout"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Server Address</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="ip"> - <property name="toolTip"> - <string><html><head/><body><p>Server address of the host</p></body></html></string> - </property> - <property name="maxLength"> - <number>253</number> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Port</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="port"> - <property name="toolTip"> - <string><html><head/><body><p>Port number the host is listening on</p></body></html></string> - </property> - <property name="maxLength"> - <number>5</number> - </property> - <property name="placeholderText"> - <string notr="true" extracomment="placeholder string that tells user default port">24872</string> - </property> - <property name="maximumSize"> - <size> - <width>65</width> - <height>50</height> - </size> - </property> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>Nickname</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="nickname"> - <property name="maxLength"> - <number>20</number> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Password</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="password"/> - </item> - </layout> - </item> - </layout> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="connect"> - <property name="text"> - <string>Connect</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp deleted file mode 100644 index ef364ee43..000000000 --- a/src/yuzu/multiplayer/host_room.cpp +++ /dev/null @@ -1,264 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <future> -#include <QColor> -#include <QImage> -#include <QList> -#include <QLocale> -#include <QMessageBox> -#include <QMetaType> -#include <QTime> -#include <QtConcurrent/QtConcurrentRun> -#include "common/logging/log.h" -#include "common/settings.h" -#include "core/core.h" -#include "core/internal_network/network_interface.h" -#include "network/announce_multiplayer_session.h" -#include "ui_host_room.h" -#include "yuzu/game_list_p.h" -#include "yuzu/main.h" -#include "yuzu/multiplayer/host_room.h" -#include "yuzu/multiplayer/message.h" -#include "yuzu/multiplayer/state.h" -#include "yuzu/multiplayer/validation.h" -#include "yuzu/uisettings.h" -#ifdef ENABLE_WEB_SERVICE -#include "web_service/verify_user_jwt.h" -#endif - -HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list, - std::shared_ptr<Core::AnnounceMultiplayerSession> session, - Core::System& system_) - : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui(std::make_unique<Ui::HostRoom>()), - announce_multiplayer_session(session), system{system_}, room_network{ - system.GetRoomNetwork()} { - ui->setupUi(this); - - // set up validation for all of the fields - ui->room_name->setValidator(validation.GetRoomName()); - ui->username->setValidator(validation.GetNickname()); - ui->port->setValidator(validation.GetPort()); - ui->port->setPlaceholderText(QString::number(Network::DefaultRoomPort)); - - // Create a proxy to the game list to display the list of preferred games - game_list = new QStandardItemModel; - UpdateGameList(list); - - proxy = new ComboBoxProxyModel; - proxy->setSourceModel(game_list); - proxy->sort(0, Qt::AscendingOrder); - ui->game_list->setModel(proxy); - - // Connect all the widgets to the appropriate events - connect(ui->host, &QPushButton::clicked, this, &HostRoomWindow::Host); - - // Restore the settings: - ui->username->setText( - QString::fromStdString(UISettings::values.multiplayer_room_nickname.GetValue())); - if (ui->username->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) { - // Use yuzu Web Service user name as nickname by default - ui->username->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue())); - } - ui->room_name->setText( - QString::fromStdString(UISettings::values.multiplayer_room_name.GetValue())); - ui->port->setText(QString::number(UISettings::values.multiplayer_room_port.GetValue())); - ui->max_player->setValue(UISettings::values.multiplayer_max_player.GetValue()); - int index = UISettings::values.multiplayer_host_type.GetValue(); - if (index < ui->host_type->count()) { - ui->host_type->setCurrentIndex(index); - } - index = ui->game_list->findData(UISettings::values.multiplayer_game_id.GetValue(), - GameListItemPath::ProgramIdRole); - if (index != -1) { - ui->game_list->setCurrentIndex(index); - } - ui->room_description->setText( - QString::fromStdString(UISettings::values.multiplayer_room_description.GetValue())); -} - -HostRoomWindow::~HostRoomWindow() = default; - -void HostRoomWindow::UpdateGameList(QStandardItemModel* list) { - game_list->clear(); - for (int i = 0; i < list->rowCount(); i++) { - auto parent = list->item(i, 0); - for (int j = 0; j < parent->rowCount(); j++) { - game_list->appendRow(parent->child(j)->clone()); - } - } -} - -void HostRoomWindow::RetranslateUi() { - ui->retranslateUi(this); -} - -std::unique_ptr<Network::VerifyUser::Backend> HostRoomWindow::CreateVerifyBackend( - bool use_validation) const { - std::unique_ptr<Network::VerifyUser::Backend> verify_backend; - if (use_validation) { -#ifdef ENABLE_WEB_SERVICE - verify_backend = - std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue()); -#else - verify_backend = std::make_unique<Network::VerifyUser::NullBackend>(); -#endif - } else { - verify_backend = std::make_unique<Network::VerifyUser::NullBackend>(); - } - return verify_backend; -} - -void HostRoomWindow::Host() { - if (!Network::GetSelectedNetworkInterface()) { - NetworkMessage::ErrorManager::ShowError( - NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); - return; - } - if (!ui->username->hasAcceptableInput()) { - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID); - return; - } - if (!ui->room_name->hasAcceptableInput()) { - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOMNAME_NOT_VALID); - return; - } - if (!ui->port->hasAcceptableInput()) { - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID); - return; - } - if (ui->game_list->currentIndex() == -1) { - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::GAME_NOT_SELECTED); - return; - } - if (system.IsPoweredOn()) { - if (!NetworkMessage::WarnGameRunning()) { - return; - } - } - if (auto member = room_network.GetRoomMember().lock()) { - if (member->GetState() == Network::RoomMember::State::Joining) { - return; - } else if (member->IsConnected()) { - auto parent = static_cast<MultiplayerState*>(parentWidget()); - if (!parent->OnCloseRoom()) { - close(); - return; - } - } - ui->host->setDisabled(true); - - const AnnounceMultiplayerRoom::GameInfo game{ - .name = ui->game_list->currentData(Qt::DisplayRole).toString().toStdString(), - .id = ui->game_list->currentData(GameListItemPath::ProgramIdRole).toULongLong(), - }; - const auto port = - ui->port->isModified() ? ui->port->text().toInt() : Network::DefaultRoomPort; - const auto password = ui->password->text().toStdString(); - const bool is_public = ui->host_type->currentIndex() == 0; - Network::Room::BanList ban_list{}; - if (ui->load_ban_list->isChecked()) { - ban_list = UISettings::values.multiplayer_ban_list; - } - if (auto room = room_network.GetRoom().lock()) { - const bool created = - room->Create(ui->room_name->text().toStdString(), - ui->room_description->toPlainText().toStdString(), "", port, password, - ui->max_player->value(), Settings::values.yuzu_username.GetValue(), - game, CreateVerifyBackend(is_public), ban_list); - if (!created) { - NetworkMessage::ErrorManager::ShowError( - NetworkMessage::ErrorManager::COULD_NOT_CREATE_ROOM); - LOG_ERROR(Network, "Could not create room!"); - ui->host->setEnabled(true); - return; - } - } - // Start the announce session if they chose Public - if (is_public) { - if (auto session = announce_multiplayer_session.lock()) { - // Register the room first to ensure verify_uid is present when we connect - WebService::WebResult result = session->Register(); - if (result.result_code != WebService::WebResult::Code::Success) { - QMessageBox::warning( - this, tr("Error"), - tr("Failed to announce the room to the public lobby. In order to host a " - "room publicly, you must have a valid yuzu account configured in " - "Emulation -> Configure -> Web. If you do not want to publish a room in " - "the public lobby, then select Unlisted instead.\nDebug Message: ") + - QString::fromStdString(result.result_string), - QMessageBox::Ok); - ui->host->setEnabled(true); - if (auto room = room_network.GetRoom().lock()) { - room->Destroy(); - } - return; - } - session->Start(); - } else { - LOG_ERROR(Network, "Starting announce session failed"); - } - } - std::string token; -#ifdef ENABLE_WEB_SERVICE - if (is_public) { - WebService::Client client(Settings::values.web_api_url.GetValue(), - Settings::values.yuzu_username.GetValue(), - Settings::values.yuzu_token.GetValue()); - if (auto room = room_network.GetRoom().lock()) { - token = client.GetExternalJWT(room->GetVerifyUID()).returned_data; - } - if (token.empty()) { - LOG_ERROR(WebService, "Could not get external JWT, verification may fail"); - } else { - LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size()); - } - } -#endif - // TODO: Check what to do with this - member->Join(ui->username->text().toStdString(), "127.0.0.1", port, 0, - Network::NoPreferredIP, password, token); - - // Store settings - UISettings::values.multiplayer_room_nickname = ui->username->text().toStdString(); - UISettings::values.multiplayer_room_name = ui->room_name->text().toStdString(); - UISettings::values.multiplayer_game_id = - ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong(); - UISettings::values.multiplayer_max_player = ui->max_player->value(); - - UISettings::values.multiplayer_host_type = ui->host_type->currentIndex(); - if (ui->port->isModified() && !ui->port->text().isEmpty()) { - UISettings::values.multiplayer_room_port = ui->port->text().toInt(); - } else { - UISettings::values.multiplayer_room_port = Network::DefaultRoomPort; - } - UISettings::values.multiplayer_room_description = - ui->room_description->toPlainText().toStdString(); - ui->host->setEnabled(true); - emit SaveConfig(); - close(); - } -} - -QVariant ComboBoxProxyModel::data(const QModelIndex& idx, int role) const { - if (role != Qt::DisplayRole) { - auto val = QSortFilterProxyModel::data(idx, role); - // If its the icon, shrink it to 16x16 - if (role == Qt::DecorationRole) - val = val.value<QImage>().scaled(16, 16, Qt::KeepAspectRatio); - return val; - } - std::string filename; - Common::SplitPath( - QSortFilterProxyModel::data(idx, GameListItemPath::FullPathRole).toString().toStdString(), - nullptr, &filename, nullptr); - QString title = QSortFilterProxyModel::data(idx, GameListItemPath::TitleRole).toString(); - return title.isEmpty() ? QString::fromStdString(filename) : title; -} - -bool ComboBoxProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { - auto leftData = left.data(GameListItemPath::TitleRole).toString(); - auto rightData = right.data(GameListItemPath::TitleRole).toString(); - return leftData.compare(rightData) < 0; -} diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h deleted file mode 100644 index ae816e2e0..000000000 --- a/src/yuzu/multiplayer/host_room.h +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <memory> -#include <QDialog> -#include <QSortFilterProxyModel> -#include <QStandardItemModel> -#include <QVariant> -#include "network/network.h" -#include "yuzu/multiplayer/chat_room.h" -#include "yuzu/multiplayer/validation.h" - -namespace Ui { -class HostRoom; -} - -namespace Core { -class System; -class AnnounceMultiplayerSession; -} // namespace Core - -class ConnectionError; -class ComboBoxProxyModel; - -class ChatMessage; - -namespace Network::VerifyUser { -class Backend; -}; - -class HostRoomWindow : public QDialog { - Q_OBJECT - -public: - explicit HostRoomWindow(QWidget* parent, QStandardItemModel* list, - std::shared_ptr<Core::AnnounceMultiplayerSession> session, - Core::System& system_); - ~HostRoomWindow(); - - /** - * Updates the dialog with a new game list model. - * This model should be the original model of the game list. - */ - void UpdateGameList(QStandardItemModel* list); - void RetranslateUi(); - -signals: - void SaveConfig(); - -private: - void Host(); - std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) const; - - std::unique_ptr<Ui::HostRoom> ui; - std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; - QStandardItemModel* game_list; - ComboBoxProxyModel* proxy; - Validation validation; - Core::System& system; - Network::RoomNetwork& room_network; -}; - -/** - * Proxy Model for the game list combo box so we can reuse the game list model while still - * displaying the fields slightly differently - */ -class ComboBoxProxyModel : public QSortFilterProxyModel { - Q_OBJECT - -public: - int columnCount(const QModelIndex& idx) const override { - return 1; - } - - QVariant data(const QModelIndex& idx, int role) const override; - - bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; -}; diff --git a/src/yuzu/multiplayer/host_room.ui b/src/yuzu/multiplayer/host_room.ui deleted file mode 100644 index d54cf49c6..000000000 --- a/src/yuzu/multiplayer/host_room.ui +++ /dev/null @@ -1,207 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>HostRoom</class> - <widget class="QWidget" name="HostRoom"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>607</width> - <height>211</height> - </rect> - </property> - <property name="windowTitle"> - <string>Create Room</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QWidget" name="settings" native="true"> - <layout class="QHBoxLayout"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <item> - <layout class="QFormLayout" name="formLayout_2"> - <property name="labelAlignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Room Name</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLineEdit" name="room_name"> - <property name="maxLength"> - <number>50</number> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Preferred Game</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QComboBox" name="game_list"/> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Max Players</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QSpinBox" name="max_player"> - <property name="minimum"> - <number>2</number> - </property> - <property name="maximum"> - <number>16</number> - </property> - <property name="value"> - <number>8</number> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QFormLayout" name="formLayout"> - <property name="labelAlignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <item row="0" column="1"> - <widget class="QLineEdit" name="username"/> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_6"> - <property name="text"> - <string>Username</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="password"> - <property name="echoMode"> - <enum>QLineEdit::PasswordEchoOnEdit</enum> - </property> - <property name="placeholderText"> - <string>(Leave blank for open game)</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLineEdit" name="port"> - <property name="inputMethodHints"> - <set>Qt::ImhDigitsOnly</set> - </property> - <property name="maxLength"> - <number>5</number> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>Password</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Port</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QLabel" name="label_7"> - <property name="text"> - <string>Room Description</string> - </property> - </widget> - </item> - <item> - <widget class="QTextEdit" name="room_description"/> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout"> - <item> - <widget class="QCheckBox" name="load_ban_list"> - <property name="text"> - <string>Load Previous Ban List</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="rightMargin"> - <number>0</number> - </property> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QComboBox" name="host_type"> - <item> - <property name="text"> - <string>Public</string> - </property> - </item> - <item> - <property name="text"> - <string>Unlisted</string> - </property> - </item> - </widget> - </item> - <item> - <widget class="QPushButton" name="host"> - <property name="text"> - <string>Host Room</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp deleted file mode 100644 index 77ac84295..000000000 --- a/src/yuzu/multiplayer/lobby.cpp +++ /dev/null @@ -1,439 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <QInputDialog> -#include <QList> -#include <QtConcurrent/QtConcurrentRun> -#include "common/logging/log.h" -#include "common/settings.h" -#include "core/core.h" -#include "core/hle/service/acc/profile_manager.h" -#include "core/internal_network/network_interface.h" -#include "network/network.h" -#include "ui_lobby.h" -#include "yuzu/game_list_p.h" -#include "yuzu/main.h" -#include "yuzu/multiplayer/client_room.h" -#include "yuzu/multiplayer/lobby.h" -#include "yuzu/multiplayer/lobby_p.h" -#include "yuzu/multiplayer/message.h" -#include "yuzu/multiplayer/state.h" -#include "yuzu/multiplayer/validation.h" -#include "yuzu/uisettings.h" -#ifdef ENABLE_WEB_SERVICE -#include "web_service/web_backend.h" -#endif - -Lobby::Lobby(QWidget* parent, QStandardItemModel* list, - std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_) - : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui(std::make_unique<Ui::Lobby>()), - announce_multiplayer_session(session), system{system_}, room_network{ - system.GetRoomNetwork()} { - ui->setupUi(this); - - // setup the watcher for background connections - watcher = new QFutureWatcher<void>; - - model = new QStandardItemModel(ui->room_list); - - // Create a proxy to the game list to get the list of games owned - game_list = new QStandardItemModel; - UpdateGameList(list); - - proxy = new LobbyFilterProxyModel(this, game_list); - proxy->setSourceModel(model); - proxy->setDynamicSortFilter(true); - proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); - proxy->setSortLocaleAware(true); - ui->room_list->setModel(proxy); - ui->room_list->header()->setSectionResizeMode(QHeaderView::Interactive); - ui->room_list->header()->stretchLastSection(); - ui->room_list->setAlternatingRowColors(true); - ui->room_list->setSelectionMode(QHeaderView::SingleSelection); - ui->room_list->setSelectionBehavior(QHeaderView::SelectRows); - ui->room_list->setVerticalScrollMode(QHeaderView::ScrollPerPixel); - ui->room_list->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); - ui->room_list->setSortingEnabled(true); - ui->room_list->setEditTriggers(QHeaderView::NoEditTriggers); - ui->room_list->setExpandsOnDoubleClick(false); - ui->room_list->setContextMenuPolicy(Qt::CustomContextMenu); - - ui->nickname->setValidator(validation.GetNickname()); - ui->nickname->setText( - QString::fromStdString(UISettings::values.multiplayer_nickname.GetValue())); - - // Try find the best nickname by default - if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("yuzu")) { - if (!Settings::values.yuzu_username.GetValue().empty()) { - ui->nickname->setText( - QString::fromStdString(Settings::values.yuzu_username.GetValue())); - } else if (!GetProfileUsername().empty()) { - ui->nickname->setText(QString::fromStdString(GetProfileUsername())); - } else { - ui->nickname->setText(QStringLiteral("yuzu")); - } - } - - // UI Buttons - connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby); - connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch); - connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned); - connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty); - connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull); - connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom); - connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom); - - // Actions - connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this, - &Lobby::OnRefreshLobby); - - // Load persistent filters after events are connected to make sure they apply - ui->search->setText( - QString::fromStdString(UISettings::values.multiplayer_filter_text.GetValue())); - ui->games_owned->setChecked(UISettings::values.multiplayer_filter_games_owned.GetValue()); - ui->hide_empty->setChecked(UISettings::values.multiplayer_filter_hide_empty.GetValue()); - ui->hide_full->setChecked(UISettings::values.multiplayer_filter_hide_full.GetValue()); -} - -Lobby::~Lobby() = default; - -void Lobby::UpdateGameList(QStandardItemModel* list) { - game_list->clear(); - for (int i = 0; i < list->rowCount(); i++) { - auto parent = list->item(i, 0); - for (int j = 0; j < parent->rowCount(); j++) { - game_list->appendRow(parent->child(j)->clone()); - } - } - if (proxy) - proxy->UpdateGameList(game_list); - ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder); -} - -void Lobby::RetranslateUi() { - ui->retranslateUi(this); -} - -QString Lobby::PasswordPrompt() { - bool ok; - const QString text = - QInputDialog::getText(this, tr("Password Required to Join"), tr("Password:"), - QLineEdit::Password, QString(), &ok); - return ok ? text : QString(); -} - -void Lobby::OnExpandRoom(const QModelIndex& index) { - QModelIndex member_index = proxy->index(index.row(), Column::MEMBER); - auto member_list = proxy->data(member_index, LobbyItemMemberList::MemberListRole).toList(); -} - -void Lobby::OnJoinRoom(const QModelIndex& source) { - if (!Network::GetSelectedNetworkInterface()) { - LOG_INFO(WebService, "Automatically selected network interface for room network."); - Network::SelectFirstNetworkInterface(); - } - - if (!Network::GetSelectedNetworkInterface()) { - NetworkMessage::ErrorManager::ShowError( - NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); - return; - } - - if (system.IsPoweredOn()) { - if (!NetworkMessage::WarnGameRunning()) { - return; - } - } - - if (const auto member = room_network.GetRoomMember().lock()) { - // Prevent the user from trying to join a room while they are already joining. - if (member->GetState() == Network::RoomMember::State::Joining) { - return; - } else if (member->IsConnected()) { - // And ask if they want to leave the room if they are already in one. - if (!NetworkMessage::WarnDisconnect()) { - return; - } - } - } - QModelIndex index = source; - // If the user double clicks on a child row (aka the player list) then use the parent instead - if (source.parent() != QModelIndex()) { - index = source.parent(); - } - if (!ui->nickname->hasAcceptableInput()) { - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID); - return; - } - - // Get a password to pass if the room is password protected - QModelIndex password_index = proxy->index(index.row(), Column::ROOM_NAME); - bool has_password = proxy->data(password_index, LobbyItemName::PasswordRole).toBool(); - const std::string password = has_password ? PasswordPrompt().toStdString() : ""; - if (has_password && password.empty()) { - return; - } - - QModelIndex connection_index = proxy->index(index.row(), Column::HOST); - const std::string nickname = ui->nickname->text().toStdString(); - const std::string ip = - proxy->data(connection_index, LobbyItemHost::HostIPRole).toString().toStdString(); - int port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt(); - const std::string verify_uid = - proxy->data(connection_index, LobbyItemHost::HostVerifyUIDRole).toString().toStdString(); - - // attempt to connect in a different thread - QFuture<void> f = QtConcurrent::run([nickname, ip, port, password, verify_uid, this] { - std::string token; -#ifdef ENABLE_WEB_SERVICE - if (!Settings::values.yuzu_username.GetValue().empty() && - !Settings::values.yuzu_token.GetValue().empty()) { - WebService::Client client(Settings::values.web_api_url.GetValue(), - Settings::values.yuzu_username.GetValue(), - Settings::values.yuzu_token.GetValue()); - token = client.GetExternalJWT(verify_uid).returned_data; - if (token.empty()) { - LOG_ERROR(WebService, "Could not get external JWT, verification may fail"); - } else { - LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size()); - } - } -#endif - if (auto room_member = room_network.GetRoomMember().lock()) { - room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredIP, password, - token); - } - }); - watcher->setFuture(f); - - // TODO(jroweboy): disable widgets and display a connecting while we wait - - // Save settings - UISettings::values.multiplayer_nickname = ui->nickname->text().toStdString(); - UISettings::values.multiplayer_filter_text = ui->search->text().toStdString(); - UISettings::values.multiplayer_filter_games_owned = ui->games_owned->isChecked(); - UISettings::values.multiplayer_filter_hide_empty = ui->hide_empty->isChecked(); - UISettings::values.multiplayer_filter_hide_full = ui->hide_full->isChecked(); - UISettings::values.multiplayer_ip = - proxy->data(connection_index, LobbyItemHost::HostIPRole).value<QString>().toStdString(); - UISettings::values.multiplayer_port = - proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt(); - emit SaveConfig(); -} - -void Lobby::ResetModel() { - model->clear(); - model->insertColumns(0, Column::TOTAL); - model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole); - model->setHeaderData(Column::ROOM_NAME, Qt::Horizontal, tr("Room Name"), Qt::DisplayRole); - model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole); - model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole); -} - -void Lobby::RefreshLobby() { - if (auto session = announce_multiplayer_session.lock()) { - ResetModel(); - ui->refresh_list->setEnabled(false); - ui->refresh_list->setText(tr("Refreshing")); - room_list_watcher.setFuture( - QtConcurrent::run([session]() { return session->GetRoomList(); })); - } else { - // TODO(jroweboy): Display an error box about announce couldn't be started - } -} - -void Lobby::OnRefreshLobby() { - AnnounceMultiplayerRoom::RoomList new_room_list = room_list_watcher.result(); - for (auto room : new_room_list) { - // find the icon for the game if this person owns that game. - QPixmap smdh_icon; - for (int r = 0; r < game_list->rowCount(); ++r) { - auto index = game_list->index(r, 0); - auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong(); - - if (game_id != 0 && room.information.preferred_game.id == game_id) { - smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>(); - } - } - - QList<QVariant> members; - for (auto member : room.members) { - QVariant var; - var.setValue(LobbyMember{QString::fromStdString(member.username), - QString::fromStdString(member.nickname), member.game.id, - QString::fromStdString(member.game.name)}); - members.append(var); - } - - auto first_item = new LobbyItemGame( - room.information.preferred_game.id, - QString::fromStdString(room.information.preferred_game.name), smdh_icon); - auto row = QList<QStandardItem*>({ - first_item, - new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)), - new LobbyItemMemberList(members, room.information.member_slots), - new LobbyItemHost(QString::fromStdString(room.information.host_username), - QString::fromStdString(room.ip), room.information.port, - QString::fromStdString(room.verify_uid)), - }); - model->appendRow(row); - // To make the rows expandable, add the member data as a child of the first column of the - // rows with people in them and have qt set them to colspan after the model is finished - // resetting - if (!room.information.description.empty()) { - first_item->appendRow( - new LobbyItemDescription(QString::fromStdString(room.information.description))); - } - if (!room.members.empty()) { - first_item->appendRow(new LobbyItemExpandedMemberList(members)); - } - } - - // Re-enable the refresh button and resize the columns - ui->refresh_list->setEnabled(true); - ui->refresh_list->setText(tr("Refresh List")); - ui->room_list->header()->stretchLastSection(); - for (int i = 0; i < Column::TOTAL - 1; ++i) { - ui->room_list->resizeColumnToContents(i); - } - - // Set the member list child items to span all columns - for (int i = 0; i < proxy->rowCount(); i++) { - auto parent = model->item(i, 0); - for (int j = 0; j < parent->rowCount(); j++) { - ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true); - } - } - - ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder); -} - -std::string Lobby::GetProfileUsername() { - const auto& current_user = - system.GetProfileManager().GetUser(Settings::values.current_user.GetValue()); - Service::Account::ProfileBase profile{}; - - if (!current_user.has_value()) { - return ""; - } - - if (!system.GetProfileManager().GetProfileBase(*current_user, profile)) { - return ""; - } - - const auto text = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); - - return text; -} - -LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list) - : QSortFilterProxyModel(parent), game_list(list) {} - -void LobbyFilterProxyModel::UpdateGameList(QStandardItemModel* list) { - game_list = list; -} - -bool LobbyFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { - // Prioritize filters by fastest to compute - - // pass over any child rows (aka row that shows the players in the room) - if (sourceParent != QModelIndex()) { - return true; - } - - // filter by empty rooms - if (filter_empty) { - QModelIndex member_list = sourceModel()->index(sourceRow, Column::MEMBER, sourceParent); - int player_count = - sourceModel()->data(member_list, LobbyItemMemberList::MemberListRole).toList().size(); - if (player_count == 0) { - return false; - } - } - - // filter by filled rooms - if (filter_full) { - QModelIndex member_list = sourceModel()->index(sourceRow, Column::MEMBER, sourceParent); - int player_count = - sourceModel()->data(member_list, LobbyItemMemberList::MemberListRole).toList().size(); - int max_players = - sourceModel()->data(member_list, LobbyItemMemberList::MaxPlayerRole).toInt(); - if (player_count >= max_players) { - return false; - } - } - - // filter by search parameters - if (!filter_search.isEmpty()) { - QModelIndex game_name = sourceModel()->index(sourceRow, Column::GAME_NAME, sourceParent); - QModelIndex room_name = sourceModel()->index(sourceRow, Column::ROOM_NAME, sourceParent); - QModelIndex host_name = sourceModel()->index(sourceRow, Column::HOST, sourceParent); - bool preferred_game_match = sourceModel() - ->data(game_name, LobbyItemGame::GameNameRole) - .toString() - .contains(filter_search, filterCaseSensitivity()); - bool room_name_match = sourceModel() - ->data(room_name, LobbyItemName::NameRole) - .toString() - .contains(filter_search, filterCaseSensitivity()); - bool username_match = sourceModel() - ->data(host_name, LobbyItemHost::HostUsernameRole) - .toString() - .contains(filter_search, filterCaseSensitivity()); - if (!preferred_game_match && !room_name_match && !username_match) { - return false; - } - } - - // filter by game owned - if (filter_owned) { - QModelIndex game_name = sourceModel()->index(sourceRow, Column::GAME_NAME, sourceParent); - QList<QModelIndex> owned_games; - for (int r = 0; r < game_list->rowCount(); ++r) { - owned_games.append(QModelIndex(game_list->index(r, 0))); - } - auto current_id = sourceModel()->data(game_name, LobbyItemGame::TitleIDRole).toLongLong(); - if (current_id == 0) { - // TODO(jroweboy): homebrew often doesn't have a game id and this hides them - return false; - } - bool owned = false; - for (const auto& game : owned_games) { - auto game_id = game_list->data(game, GameListItemPath::ProgramIdRole).toLongLong(); - if (current_id == game_id) { - owned = true; - } - } - if (!owned) { - return false; - } - } - - return true; -} - -void LobbyFilterProxyModel::sort(int column, Qt::SortOrder order) { - sourceModel()->sort(column, order); -} - -void LobbyFilterProxyModel::SetFilterOwned(bool filter) { - filter_owned = filter; - invalidate(); -} - -void LobbyFilterProxyModel::SetFilterEmpty(bool filter) { - filter_empty = filter; - invalidate(); -} - -void LobbyFilterProxyModel::SetFilterFull(bool filter) { - filter_full = filter; - invalidate(); -} - -void LobbyFilterProxyModel::SetFilterSearch(const QString& filter) { - filter_search = filter; - invalidate(); -} diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h deleted file mode 100644 index e78c9cae3..000000000 --- a/src/yuzu/multiplayer/lobby.h +++ /dev/null @@ -1,138 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <memory> -#include <QDialog> -#include <QFutureWatcher> -#include <QSortFilterProxyModel> -#include <QStandardItemModel> -#include "common/announce_multiplayer_room.h" -#include "network/announce_multiplayer_session.h" -#include "network/network.h" -#include "yuzu/multiplayer/validation.h" - -namespace Ui { -class Lobby; -} - -class LobbyModel; -class LobbyFilterProxyModel; - -namespace Core { -class System; -} - -/** - * Listing of all public games pulled from services. The lobby should be simple enough for users to - * find the game they want to play, and join it. - */ -class Lobby : public QDialog { - Q_OBJECT - -public: - explicit Lobby(QWidget* parent, QStandardItemModel* list, - std::shared_ptr<Core::AnnounceMultiplayerSession> session, - Core::System& system_); - ~Lobby() override; - - /** - * Updates the lobby with a new game list model. - * This model should be the original model of the game list. - */ - void UpdateGameList(QStandardItemModel* list); - void RetranslateUi(); - -public slots: - /** - * Begin the process to pull the latest room list from web services. After the listing is - * returned from web services, `LobbyRefreshed` will be signalled - */ - void RefreshLobby(); - -private slots: - /** - * Pulls the list of rooms from network and fills out the lobby model with the results - */ - void OnRefreshLobby(); - - /** - * Handler for single clicking on a room in the list. Expands the treeitem to show player - * information for the people in the room - * - * index - The row of the proxy model that the user wants to join. - */ - void OnExpandRoom(const QModelIndex&); - - /** - * Handler for double clicking on a room in the list. Gathers the host ip and port and attempts - * to connect. Will also prompt for a password in case one is required. - * - * index - The row of the proxy model that the user wants to join. - */ - void OnJoinRoom(const QModelIndex&); - -signals: - void StateChanged(const Network::RoomMember::State&); - void SaveConfig(); - -private: - std::string GetProfileUsername(); - - /** - * Removes all entries in the Lobby before refreshing. - */ - void ResetModel(); - - /** - * Prompts for a password. Returns an empty QString if the user either did not provide a - * password or if the user closed the window. - */ - QString PasswordPrompt(); - - std::unique_ptr<Ui::Lobby> ui; - - QStandardItemModel* model{}; - QStandardItemModel* game_list{}; - LobbyFilterProxyModel* proxy{}; - - QFutureWatcher<AnnounceMultiplayerRoom::RoomList> room_list_watcher; - std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; - QFutureWatcher<void>* watcher; - Validation validation; - Core::System& system; - Network::RoomNetwork& room_network; -}; - -/** - * Proxy Model for filtering the lobby - */ -class LobbyFilterProxyModel : public QSortFilterProxyModel { - Q_OBJECT; - -public: - explicit LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list); - - /** - * Updates the filter with a new game list model. - * This model should be the processed one created by the Lobby. - */ - void UpdateGameList(QStandardItemModel* list); - - bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; - void sort(int column, Qt::SortOrder order) override; - -public slots: - void SetFilterOwned(bool); - void SetFilterEmpty(bool); - void SetFilterFull(bool); - void SetFilterSearch(const QString&); - -private: - QStandardItemModel* game_list; - bool filter_owned = false; - bool filter_empty = false; - bool filter_full = false; - QString filter_search; -}; diff --git a/src/yuzu/multiplayer/lobby.ui b/src/yuzu/multiplayer/lobby.ui deleted file mode 100644 index 0ef0ef762..000000000 --- a/src/yuzu/multiplayer/lobby.ui +++ /dev/null @@ -1,130 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>Lobby</class> - <widget class="QWidget" name="Lobby"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>903</width> - <height>487</height> - </rect> - </property> - <property name="windowTitle"> - <string>Public Room Browser</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <property name="spacing"> - <number>3</number> - </property> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <property name="spacing"> - <number>6</number> - </property> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_5"> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Nickname</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="nickname"> - <property name="placeholderText"> - <string>Nickname</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Filters</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="search"> - <property name="placeholderText"> - <string>Search</string> - </property> - <property name="clearButtonEnabled"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="games_owned"> - <property name="text"> - <string>Games I Own</string> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="hide_empty"> - <property name="text"> - <string>Hide Empty Rooms</string> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="hide_full"> - <property name="text"> - <string>Hide Full Rooms</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="refresh_list"> - <property name="text"> - <string>Refresh Lobby</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </item> - <item> - <widget class="QTreeView" name="room_list"/> - </item> - <item> - <widget class="QWidget" name="widget" native="true"/> - </item> - </layout> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/yuzu/multiplayer/lobby_p.h b/src/yuzu/multiplayer/lobby_p.h deleted file mode 100644 index 77ec1fcde..000000000 --- a/src/yuzu/multiplayer/lobby_p.h +++ /dev/null @@ -1,268 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <utility> -#include <QPixmap> -#include <QStandardItem> -#include <QStandardItemModel> -#include "common/common_types.h" - -namespace Column { -enum List { - GAME_NAME, - ROOM_NAME, - MEMBER, - HOST, - TOTAL, -}; -} - -class LobbyItem : public QStandardItem { -public: - LobbyItem() = default; - explicit LobbyItem(const QString& string) : QStandardItem(string) {} - virtual ~LobbyItem() override = default; -}; - -class LobbyItemName : public LobbyItem { -public: - static const int NameRole = Qt::UserRole + 1; - static const int PasswordRole = Qt::UserRole + 2; - - LobbyItemName() = default; - explicit LobbyItemName(bool has_password, QString name) : LobbyItem() { - setData(name, NameRole); - setData(has_password, PasswordRole); - } - - QVariant data(int role) const override { - if (role == Qt::DecorationRole) { - bool has_password = data(PasswordRole).toBool(); - return has_password ? QIcon::fromTheme(QStringLiteral("lock")).pixmap(16) : QIcon(); - } - if (role != Qt::DisplayRole) { - return LobbyItem::data(role); - } - return data(NameRole).toString(); - } - - bool operator<(const QStandardItem& other) const override { - return data(NameRole).toString().localeAwareCompare(other.data(NameRole).toString()) < 0; - } -}; - -class LobbyItemDescription : public LobbyItem { -public: - static const int DescriptionRole = Qt::UserRole + 1; - - LobbyItemDescription() = default; - explicit LobbyItemDescription(QString description) { - setData(description, DescriptionRole); - } - - QVariant data(int role) const override { - if (role != Qt::DisplayRole) { - return LobbyItem::data(role); - } - auto description = data(DescriptionRole).toString(); - description.prepend(QStringLiteral("Description: ")); - return description; - } - - bool operator<(const QStandardItem& other) const override { - return data(DescriptionRole) - .toString() - .localeAwareCompare(other.data(DescriptionRole).toString()) < 0; - } -}; - -class LobbyItemGame : public LobbyItem { -public: - static const int TitleIDRole = Qt::UserRole + 1; - static const int GameNameRole = Qt::UserRole + 2; - static const int GameIconRole = Qt::UserRole + 3; - - LobbyItemGame() = default; - explicit LobbyItemGame(u64 title_id, QString game_name, QPixmap smdh_icon) { - setData(static_cast<unsigned long long>(title_id), TitleIDRole); - setData(game_name, GameNameRole); - if (!smdh_icon.isNull()) { - setData(smdh_icon, GameIconRole); - } else { - setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(32), GameIconRole); - } - } - - QVariant data(int role) const override { - if (role == Qt::DecorationRole) { - auto val = data(GameIconRole); - if (val.isValid()) { - val = val.value<QPixmap>().scaled(32, 32, Qt::KeepAspectRatio, - Qt::TransformationMode::SmoothTransformation); - } else { - auto blank_image = QPixmap(32, 32); - blank_image.fill(Qt::black); - val = blank_image; - } - return val; - } else if (role != Qt::DisplayRole) { - return LobbyItem::data(role); - } - return data(GameNameRole).toString(); - } - - bool operator<(const QStandardItem& other) const override { - return data(GameNameRole) - .toString() - .localeAwareCompare(other.data(GameNameRole).toString()) < 0; - } -}; - -class LobbyItemHost : public LobbyItem { -public: - static const int HostUsernameRole = Qt::UserRole + 1; - static const int HostIPRole = Qt::UserRole + 2; - static const int HostPortRole = Qt::UserRole + 3; - static const int HostVerifyUIDRole = Qt::UserRole + 4; - - LobbyItemHost() = default; - explicit LobbyItemHost(QString username, QString ip, u16 port, QString verify_uid) { - setData(username, HostUsernameRole); - setData(ip, HostIPRole); - setData(port, HostPortRole); - setData(verify_uid, HostVerifyUIDRole); - } - - QVariant data(int role) const override { - if (role != Qt::DisplayRole) { - return LobbyItem::data(role); - } - return data(HostUsernameRole).toString(); - } - - bool operator<(const QStandardItem& other) const override { - return data(HostUsernameRole) - .toString() - .localeAwareCompare(other.data(HostUsernameRole).toString()) < 0; - } -}; - -class LobbyMember { -public: - LobbyMember() = default; - LobbyMember(const LobbyMember& other) = default; - explicit LobbyMember(QString username_, QString nickname_, u64 title_id_, QString game_name_) - : username(std::move(username_)), nickname(std::move(nickname_)), title_id(title_id_), - game_name(std::move(game_name_)) {} - ~LobbyMember() = default; - - QString GetName() const { - if (username.isEmpty() || username == nickname) { - return nickname; - } else { - return QStringLiteral("%1 (%2)").arg(nickname, username); - } - } - u64 GetTitleId() const { - return title_id; - } - QString GetGameName() const { - return game_name; - } - -private: - QString username; - QString nickname; - u64 title_id; - QString game_name; -}; - -Q_DECLARE_METATYPE(LobbyMember); - -class LobbyItemMemberList : public LobbyItem { -public: - static const int MemberListRole = Qt::UserRole + 1; - static const int MaxPlayerRole = Qt::UserRole + 2; - - LobbyItemMemberList() = default; - explicit LobbyItemMemberList(QList<QVariant> members, u32 max_players) { - setData(members, MemberListRole); - setData(max_players, MaxPlayerRole); - } - - QVariant data(int role) const override { - switch (role) { - case Qt::DisplayRole: { - auto members = data(MemberListRole).toList(); - return QStringLiteral("%1 / %2 ") - .arg(QString::number(members.size()), data(MaxPlayerRole).toString()); - } - case Qt::ForegroundRole: { - auto members = data(MemberListRole).toList(); - auto max_players = data(MaxPlayerRole).toInt(); - const QColor room_full_color(255, 48, 32); - const QColor room_almost_full_color(255, 140, 32); - const QColor room_has_players_color(32, 160, 32); - const QColor room_empty_color(128, 128, 128); - - if (members.size() >= max_players) { - return QBrush(room_full_color); - } else if (members.size() == (max_players - 1)) { - return QBrush(room_almost_full_color); - } else if (members.size() == 0) { - return QBrush(room_empty_color); - } else if (members.size() > 0 && members.size() < (max_players - 1)) { - return QBrush(room_has_players_color); - } - // FIXME: How to return a value that tells Qt not to modify the - // text color from the default (as if Qt::ForegroundRole wasn't overridden)? - return QBrush(nullptr); - } - default: - return LobbyItem::data(role); - } - } - - bool operator<(const QStandardItem& other) const override { - // sort by rooms that have the most players - int left_members = data(MemberListRole).toList().size(); - int right_members = other.data(MemberListRole).toList().size(); - return left_members < right_members; - } -}; - -/** - * Member information for when a lobby is expanded in the UI - */ -class LobbyItemExpandedMemberList : public LobbyItem { -public: - static const int MemberListRole = Qt::UserRole + 1; - - LobbyItemExpandedMemberList() = default; - explicit LobbyItemExpandedMemberList(QList<QVariant> members) { - setData(members, MemberListRole); - } - - QVariant data(int role) const override { - if (role != Qt::DisplayRole) { - return LobbyItem::data(role); - } - auto members = data(MemberListRole).toList(); - QString out; - bool first = true; - for (const auto& member : members) { - if (!first) - out.append(QStringLiteral("\n")); - const auto& m = member.value<LobbyMember>(); - if (m.GetGameName().isEmpty()) { - out += QString(QObject::tr("%1 is not playing a game")).arg(m.GetName()); - } else { - out += QString(QObject::tr("%1 is playing %2")).arg(m.GetName(), m.GetGameName()); - } - first = false; - } - return out; - } -}; diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp deleted file mode 100644 index 6d8f18274..000000000 --- a/src/yuzu/multiplayer/message.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <QMessageBox> -#include <QString> - -#include "yuzu/multiplayer/message.h" - -namespace NetworkMessage { -const ConnectionError ErrorManager::USERNAME_NOT_VALID( - QT_TR_NOOP("Username is not valid. Must be 4 to 20 alphanumeric characters.")); -const ConnectionError ErrorManager::ROOMNAME_NOT_VALID( - QT_TR_NOOP("Room name is not valid. Must be 4 to 20 alphanumeric characters.")); -const ConnectionError ErrorManager::USERNAME_NOT_VALID_SERVER( - QT_TR_NOOP("Username is already in use or not valid. Please choose another.")); -const ConnectionError ErrorManager::IP_ADDRESS_NOT_VALID( - QT_TR_NOOP("IP is not a valid IPv4 address.")); -const ConnectionError ErrorManager::PORT_NOT_VALID( - QT_TR_NOOP("Port must be a number between 0 to 65535.")); -const ConnectionError ErrorManager::GAME_NOT_SELECTED(QT_TR_NOOP( - "You must choose a Preferred Game to host a room. If you do not have any games in your game " - "list yet, add a game folder by clicking on the plus icon in the game list.")); -const ConnectionError ErrorManager::NO_INTERNET( - QT_TR_NOOP("Unable to find an internet connection. Check your internet settings.")); -const ConnectionError ErrorManager::UNABLE_TO_CONNECT( - QT_TR_NOOP("Unable to connect to the host. Verify that the connection settings are correct. If " - "you still cannot connect, contact the room host and verify that the host is " - "properly configured with the external port forwarded.")); -const ConnectionError ErrorManager::ROOM_IS_FULL( - QT_TR_NOOP("Unable to connect to the room because it is already full.")); -const ConnectionError ErrorManager::COULD_NOT_CREATE_ROOM( - QT_TR_NOOP("Creating a room failed. Please retry. Restarting yuzu might be necessary.")); -const ConnectionError ErrorManager::HOST_BANNED( - QT_TR_NOOP("The host of the room has banned you. Speak with the host to unban you " - "or try a different room.")); -const ConnectionError ErrorManager::WRONG_VERSION( - QT_TR_NOOP("Version mismatch! Please update to the latest version of yuzu. If the problem " - "persists, contact the room host and ask them to update the server.")); -const ConnectionError ErrorManager::WRONG_PASSWORD(QT_TR_NOOP("Incorrect password.")); -const ConnectionError ErrorManager::GENERIC_ERROR(QT_TR_NOOP( - "An unknown error occurred. If this error continues to occur, please open an issue")); -const ConnectionError ErrorManager::LOST_CONNECTION( - QT_TR_NOOP("Connection to room lost. Try to reconnect.")); -const ConnectionError ErrorManager::HOST_KICKED( - QT_TR_NOOP("You have been kicked by the room host.")); -const ConnectionError ErrorManager::IP_COLLISION( - QT_TR_NOOP("IP address is already in use. Please choose another.")); -const ConnectionError ErrorManager::PERMISSION_DENIED( - QT_TR_NOOP("You do not have enough permission to perform this action.")); -const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP( - "The user you are trying to kick/ban could not be found.\nThey may have left the room.")); -const ConnectionError ErrorManager::NO_INTERFACE_SELECTED(QT_TR_NOOP( - "No valid network interface is selected.\nPlease go to Configure -> System -> Network and " - "make a selection.")); - -static bool WarnMessage(const std::string& title, const std::string& text) { - return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()), - QObject::tr(text.c_str()), - QMessageBox::Ok | QMessageBox::Cancel); -} - -void ErrorManager::ShowError(const ConnectionError& e) { - QMessageBox::critical(nullptr, tr("Error"), tr(e.GetString().c_str())); -} - -bool WarnGameRunning() { - return WarnMessage( - QT_TR_NOOP("Game already running"), - QT_TR_NOOP("Joining a room when the game is already running is discouraged " - "and can cause the room feature not to work correctly.\nProceed anyway?")); -} - -bool WarnCloseRoom() { - return WarnMessage( - QT_TR_NOOP("Leave Room"), - QT_TR_NOOP("You are about to close the room. Any network connections will be closed.")); -} - -bool WarnDisconnect() { - return WarnMessage( - QT_TR_NOOP("Disconnect"), - QT_TR_NOOP("You are about to leave the room. Any network connections will be closed.")); -} - -} // namespace NetworkMessage diff --git a/src/yuzu/multiplayer/message.h b/src/yuzu/multiplayer/message.h deleted file mode 100644 index f038b9a1f..000000000 --- a/src/yuzu/multiplayer/message.h +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <utility> - -namespace NetworkMessage { - -class ConnectionError { - -public: - explicit ConnectionError(std::string str) : err(std::move(str)) {} - const std::string& GetString() const { - return err; - } - -private: - std::string err; -}; - -class ErrorManager : QObject { - Q_OBJECT -public: - /// When the nickname is considered invalid by the client - static const ConnectionError USERNAME_NOT_VALID; - static const ConnectionError ROOMNAME_NOT_VALID; - /// When the nickname is considered invalid by the room server - static const ConnectionError USERNAME_NOT_VALID_SERVER; - static const ConnectionError IP_ADDRESS_NOT_VALID; - static const ConnectionError PORT_NOT_VALID; - static const ConnectionError GAME_NOT_SELECTED; - static const ConnectionError NO_INTERNET; - static const ConnectionError UNABLE_TO_CONNECT; - static const ConnectionError ROOM_IS_FULL; - static const ConnectionError COULD_NOT_CREATE_ROOM; - static const ConnectionError HOST_BANNED; - static const ConnectionError WRONG_VERSION; - static const ConnectionError WRONG_PASSWORD; - static const ConnectionError GENERIC_ERROR; - static const ConnectionError LOST_CONNECTION; - static const ConnectionError HOST_KICKED; - static const ConnectionError IP_COLLISION; - static const ConnectionError PERMISSION_DENIED; - static const ConnectionError NO_SUCH_USER; - static const ConnectionError NO_INTERFACE_SELECTED; - /** - * Shows a standard QMessageBox with a error message - */ - static void ShowError(const ConnectionError& e); -}; - -/** - * Show a standard QMessageBox with a warning message about joining a room when - * the game is already running - * return true if the user wants to close the network connection - */ -bool WarnGameRunning(); - -/** - * Show a standard QMessageBox with a warning message about leaving the room - * return true if the user wants to close the network connection - */ -bool WarnCloseRoom(); - -/** - * Show a standard QMessageBox with a warning message about disconnecting from the room - * return true if the user wants to disconnect - */ -bool WarnDisconnect(); - -} // namespace NetworkMessage diff --git a/src/yuzu/multiplayer/moderation_dialog.cpp b/src/yuzu/multiplayer/moderation_dialog.cpp deleted file mode 100644 index c9b8ed397..000000000 --- a/src/yuzu/multiplayer/moderation_dialog.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <QStandardItem> -#include <QStandardItemModel> -#include "network/network.h" -#include "network/room_member.h" -#include "ui_moderation_dialog.h" -#include "yuzu/multiplayer/moderation_dialog.h" - -namespace Column { -enum { - SUBJECT, - TYPE, - COUNT, -}; -} - -ModerationDialog::ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent) - : QDialog(parent), ui(std::make_unique<Ui::ModerationDialog>()), room_network{room_network_} { - ui->setupUi(this); - - qRegisterMetaType<Network::Room::BanList>(); - - if (auto member = room_network.GetRoomMember().lock()) { - callback_handle_status_message = member->BindOnStatusMessageReceived( - [this](const Network::StatusMessageEntry& status_message) { - emit StatusMessageReceived(status_message); - }); - connect(this, &ModerationDialog::StatusMessageReceived, this, - &ModerationDialog::OnStatusMessageReceived); - callback_handle_ban_list = member->BindOnBanListReceived( - [this](const Network::Room::BanList& ban_list) { emit BanListReceived(ban_list); }); - connect(this, &ModerationDialog::BanListReceived, this, &ModerationDialog::PopulateBanList); - } - - // Initialize the UI - model = new QStandardItemModel(ui->ban_list_view); - model->insertColumns(0, Column::COUNT); - model->setHeaderData(Column::SUBJECT, Qt::Horizontal, tr("Subject")); - model->setHeaderData(Column::TYPE, Qt::Horizontal, tr("Type")); - - ui->ban_list_view->setModel(model); - - // Load the ban list in background - LoadBanList(); - - connect(ui->refresh, &QPushButton::clicked, this, [this] { LoadBanList(); }); - connect(ui->unban, &QPushButton::clicked, this, [this] { - auto index = ui->ban_list_view->currentIndex(); - SendUnbanRequest(model->item(index.row(), 0)->text()); - }); - connect(ui->ban_list_view, &QTreeView::clicked, [this] { ui->unban->setEnabled(true); }); -} - -ModerationDialog::~ModerationDialog() { - if (callback_handle_status_message) { - if (auto room = room_network.GetRoomMember().lock()) { - room->Unbind(callback_handle_status_message); - } - } - - if (callback_handle_ban_list) { - if (auto room = room_network.GetRoomMember().lock()) { - room->Unbind(callback_handle_ban_list); - } - } -} - -void ModerationDialog::LoadBanList() { - if (auto room = room_network.GetRoomMember().lock()) { - ui->refresh->setEnabled(false); - ui->refresh->setText(tr("Refreshing")); - ui->unban->setEnabled(false); - room->RequestBanList(); - } -} - -void ModerationDialog::PopulateBanList(const Network::Room::BanList& ban_list) { - model->removeRows(0, model->rowCount()); - for (const auto& username : ban_list.first) { - QStandardItem* subject_item = new QStandardItem(QString::fromStdString(username)); - QStandardItem* type_item = new QStandardItem(tr("Forum Username")); - model->invisibleRootItem()->appendRow({subject_item, type_item}); - } - for (const auto& ip : ban_list.second) { - QStandardItem* subject_item = new QStandardItem(QString::fromStdString(ip)); - QStandardItem* type_item = new QStandardItem(tr("IP Address")); - model->invisibleRootItem()->appendRow({subject_item, type_item}); - } - for (int i = 0; i < Column::COUNT - 1; ++i) { - ui->ban_list_view->resizeColumnToContents(i); - } - ui->refresh->setEnabled(true); - ui->refresh->setText(tr("Refresh")); - ui->unban->setEnabled(false); -} - -void ModerationDialog::SendUnbanRequest(const QString& subject) { - if (auto room = room_network.GetRoomMember().lock()) { - room->SendModerationRequest(Network::IdModUnban, subject.toStdString()); - } -} - -void ModerationDialog::OnStatusMessageReceived(const Network::StatusMessageEntry& status_message) { - if (status_message.type != Network::IdMemberBanned && - status_message.type != Network::IdAddressUnbanned) - return; - - // Update the ban list for ban/unban - LoadBanList(); -} diff --git a/src/yuzu/multiplayer/moderation_dialog.h b/src/yuzu/multiplayer/moderation_dialog.h deleted file mode 100644 index e9e5daff7..000000000 --- a/src/yuzu/multiplayer/moderation_dialog.h +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <memory> -#include <optional> -#include <QDialog> -#include "network/room.h" -#include "network/room_member.h" - -namespace Ui { -class ModerationDialog; -} - -class QStandardItemModel; - -class ModerationDialog : public QDialog { - Q_OBJECT - -public: - explicit ModerationDialog(Network::RoomNetwork& room_network_, QWidget* parent = nullptr); - ~ModerationDialog(); - -signals: - void StatusMessageReceived(const Network::StatusMessageEntry&); - void BanListReceived(const Network::Room::BanList&); - -private: - void LoadBanList(); - void PopulateBanList(const Network::Room::BanList& ban_list); - void SendUnbanRequest(const QString& subject); - void OnStatusMessageReceived(const Network::StatusMessageEntry& status_message); - - std::unique_ptr<Ui::ModerationDialog> ui; - QStandardItemModel* model; - Network::RoomMember::CallbackHandle<Network::StatusMessageEntry> callback_handle_status_message; - Network::RoomMember::CallbackHandle<Network::Room::BanList> callback_handle_ban_list; - - Network::RoomNetwork& room_network; -}; - -Q_DECLARE_METATYPE(Network::Room::BanList); diff --git a/src/yuzu/multiplayer/moderation_dialog.ui b/src/yuzu/multiplayer/moderation_dialog.ui deleted file mode 100644 index 808d99414..000000000 --- a/src/yuzu/multiplayer/moderation_dialog.ui +++ /dev/null @@ -1,84 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ModerationDialog</class> - <widget class="QDialog" name="ModerationDialog"> - <property name="windowTitle"> - <string>Moderation</string> - </property> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>500</width> - <height>300</height> - </rect> - </property> - <layout class="QVBoxLayout"> - <item> - <widget class="QGroupBox" name="ban_list_group_box"> - <property name="title"> - <string>Ban List</string> - </property> - <layout class="QVBoxLayout"> - <item> - <layout class="QHBoxLayout"> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="refresh"> - <property name="text"> - <string>Refreshing</string> - </property> - <property name="enabled"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="unban"> - <property name="text"> - <string>Unban</string> - </property> - <property name="enabled"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QTreeView" name="ban_list_view"/> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Ok</set> - </property> - </widget> - </item> - </layout> - </widget> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>ModerationDialog</receiver> - <slot>accept()</slot> - </connection> - </connections> - <resources/> -</ui> diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp deleted file mode 100644 index d82ca9aee..000000000 --- a/src/yuzu/multiplayer/state.cpp +++ /dev/null @@ -1,336 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <QAction> -#include <QApplication> -#include <QIcon> -#include <QMessageBox> -#include <QStandardItemModel> -#include "common/announce_multiplayer_room.h" -#include "common/logging/log.h" -#include "core/core.h" -#include "yuzu/game_list.h" -#include "yuzu/multiplayer/client_room.h" -#include "yuzu/multiplayer/direct_connect.h" -#include "yuzu/multiplayer/host_room.h" -#include "yuzu/multiplayer/lobby.h" -#include "yuzu/multiplayer/message.h" -#include "yuzu/multiplayer/state.h" -#include "yuzu/uisettings.h" -#include "yuzu/util/clickable_label.h" - -MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_, - QAction* leave_room_, QAction* show_room_, Core::System& system_) - : QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_), - show_room(show_room_), system{system_}, room_network{system.GetRoomNetwork()} { - if (auto member = room_network.GetRoomMember().lock()) { - // register the network structs to use in slots and signals - state_callback_handle = member->BindOnStateChanged( - [this](const Network::RoomMember::State& state) { emit NetworkStateChanged(state); }); - connect(this, &MultiplayerState::NetworkStateChanged, this, - &MultiplayerState::OnNetworkStateChanged); - error_callback_handle = member->BindOnError( - [this](const Network::RoomMember::Error& error) { emit NetworkError(error); }); - connect(this, &MultiplayerState::NetworkError, this, &MultiplayerState::OnNetworkError); - } - - qRegisterMetaType<Network::RoomMember::State>(); - qRegisterMetaType<Network::RoomMember::Error>(); - qRegisterMetaType<WebService::WebResult>(); - announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>(room_network); - announce_multiplayer_session->BindErrorCallback( - [this](const WebService::WebResult& result) { emit AnnounceFailed(result); }); - connect(this, &MultiplayerState::AnnounceFailed, this, &MultiplayerState::OnAnnounceFailed); - - status_text = new ClickableLabel(this); - status_icon = new ClickableLabel(this); - - connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); - connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); - - connect(static_cast<QApplication*>(QApplication::instance()), &QApplication::focusChanged, this, - [this](QWidget* /*old*/, QWidget* now) { - if (client_room && client_room->isAncestorOf(now)) { - HideNotification(); - } - }); - - retranslateUi(); -} - -MultiplayerState::~MultiplayerState() = default; - -void MultiplayerState::Close() { - if (state_callback_handle) { - if (auto member = room_network.GetRoomMember().lock()) { - member->Unbind(state_callback_handle); - } - } - - if (error_callback_handle) { - if (auto member = room_network.GetRoomMember().lock()) { - member->Unbind(error_callback_handle); - } - } - if (host_room) { - host_room->close(); - } - if (direct_connect) { - direct_connect->close(); - } - if (client_room) { - client_room->close(); - } - if (lobby) { - lobby->close(); - } -} - -void MultiplayerState::retranslateUi() { - status_text->setToolTip(tr("Current connection status")); - - UpdateNotificationStatus(); - - if (lobby) { - lobby->RetranslateUi(); - } - if (host_room) { - host_room->RetranslateUi(); - } - if (client_room) { - client_room->RetranslateUi(); - } - if (direct_connect) { - direct_connect->RetranslateUi(); - } -} - -void MultiplayerState::SetNotificationStatus(NotificationStatus status) { - notification_status = status; - UpdateNotificationStatus(); -} - -void MultiplayerState::UpdateNotificationStatus() { - switch (notification_status) { - case NotificationStatus::Uninitialized: - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); - status_text->setText(tr("Not Connected. Click here to find a room!")); - leave_room->setEnabled(false); - show_room->setEnabled(false); - break; - case NotificationStatus::Disconnected: - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); - status_text->setText(tr("Not Connected")); - leave_room->setEnabled(false); - show_room->setEnabled(false); - break; - case NotificationStatus::Connected: - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); - status_text->setText(tr("Connected")); - leave_room->setEnabled(true); - show_room->setEnabled(true); - break; - case NotificationStatus::Notification: - status_icon->setPixmap( - QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16)); - status_text->setText(tr("New Messages Received")); - leave_room->setEnabled(true); - show_room->setEnabled(true); - break; - } - - // Clean up status bar if game is running - if (system.IsPoweredOn()) { - status_text->clear(); - } -} - -void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) { - LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state)); - if (state == Network::RoomMember::State::Joined || - state == Network::RoomMember::State::Moderator) { - - OnOpenNetworkRoom(); - SetNotificationStatus(NotificationStatus::Connected); - } else { - SetNotificationStatus(NotificationStatus::Disconnected); - } - - current_state = state; -} - -void MultiplayerState::OnNetworkError(const Network::RoomMember::Error& error) { - LOG_DEBUG(Frontend, "Network Error: {}", Network::GetErrorStr(error)); - switch (error) { - case Network::RoomMember::Error::LostConnection: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::LOST_CONNECTION); - break; - case Network::RoomMember::Error::HostKicked: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::HOST_KICKED); - break; - case Network::RoomMember::Error::CouldNotConnect: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::UNABLE_TO_CONNECT); - break; - case Network::RoomMember::Error::NameCollision: - NetworkMessage::ErrorManager::ShowError( - NetworkMessage::ErrorManager::USERNAME_NOT_VALID_SERVER); - break; - case Network::RoomMember::Error::IpCollision: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_COLLISION); - break; - case Network::RoomMember::Error::RoomIsFull: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOM_IS_FULL); - break; - case Network::RoomMember::Error::WrongPassword: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::WRONG_PASSWORD); - break; - case Network::RoomMember::Error::WrongVersion: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::WRONG_VERSION); - break; - case Network::RoomMember::Error::HostBanned: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::HOST_BANNED); - break; - case Network::RoomMember::Error::UnknownError: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::UNABLE_TO_CONNECT); - break; - case Network::RoomMember::Error::PermissionDenied: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PERMISSION_DENIED); - break; - case Network::RoomMember::Error::NoSuchUser: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::NO_SUCH_USER); - break; - } -} - -void MultiplayerState::OnAnnounceFailed(const WebService::WebResult& result) { - announce_multiplayer_session->Stop(); - QMessageBox::warning(this, tr("Error"), - tr("Failed to update the room information. Please check your Internet " - "connection and try hosting the room again.\nDebug Message: ") + - QString::fromStdString(result.result_string), - QMessageBox::Ok); -} - -void MultiplayerState::OnSaveConfig() { - emit SaveConfig(); -} - -void MultiplayerState::UpdateThemedIcons() { - if (show_notification) { - status_icon->setPixmap( - QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16)); - } else if (current_state == Network::RoomMember::State::Joined || - current_state == Network::RoomMember::State::Moderator) { - - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); - } else { - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); - } - if (client_room) - client_room->UpdateIconDisplay(); -} - -static void BringWidgetToFront(QWidget* widget) { - widget->show(); - widget->activateWindow(); - widget->raise(); -} - -void MultiplayerState::OnViewLobby() { - if (lobby == nullptr) { - lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system); - connect(lobby, &Lobby::SaveConfig, this, &MultiplayerState::OnSaveConfig); - } - lobby->RefreshLobby(); - BringWidgetToFront(lobby); -} - -void MultiplayerState::OnCreateRoom() { - if (host_room == nullptr) { - host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system); - connect(host_room, &HostRoomWindow::SaveConfig, this, &MultiplayerState::OnSaveConfig); - } - BringWidgetToFront(host_room); -} - -bool MultiplayerState::OnCloseRoom() { - if (!NetworkMessage::WarnCloseRoom()) - return false; - if (auto room = room_network.GetRoom().lock()) { - // if you are in a room, leave it - if (auto member = room_network.GetRoomMember().lock()) { - member->Leave(); - LOG_DEBUG(Frontend, "Left the room (as a client)"); - } - - // if you are hosting a room, also stop hosting - if (room->GetState() != Network::Room::State::Open) { - return true; - } - // Save ban list - UISettings::values.multiplayer_ban_list = room->GetBanList(); - - room->Destroy(); - announce_multiplayer_session->Stop(); - LOG_DEBUG(Frontend, "Closed the room (as a server)"); - } - return true; -} - -void MultiplayerState::ShowNotification() { - if (client_room && client_room->isAncestorOf(QApplication::focusWidget())) - return; // Do not show notification if the chat window currently has focus - show_notification = true; - QApplication::alert(nullptr); - QApplication::beep(); - SetNotificationStatus(NotificationStatus::Notification); -} - -void MultiplayerState::HideNotification() { - show_notification = false; - SetNotificationStatus(NotificationStatus::Connected); -} - -void MultiplayerState::OnOpenNetworkRoom() { - if (auto member = room_network.GetRoomMember().lock()) { - if (member->IsConnected()) { - if (client_room == nullptr) { - client_room = new ClientRoomWindow(this, room_network); - connect(client_room, &ClientRoomWindow::ShowNotification, this, - &MultiplayerState::ShowNotification); - } - BringWidgetToFront(client_room); - return; - } - } - // If the user is not a member of a room, show the lobby instead. - // This is currently only used on the clickable label in the status bar - OnViewLobby(); -} - -void MultiplayerState::OnDirectConnectToRoom() { - if (direct_connect == nullptr) { - direct_connect = new DirectConnectWindow(system, this); - connect(direct_connect, &DirectConnectWindow::SaveConfig, this, - &MultiplayerState::OnSaveConfig); - } - BringWidgetToFront(direct_connect); -} - -bool MultiplayerState::IsHostingPublicRoom() const { - return announce_multiplayer_session->IsRunning(); -} - -void MultiplayerState::UpdateCredentials() { - announce_multiplayer_session->UpdateCredentials(); -} - -void MultiplayerState::UpdateGameList(QStandardItemModel* game_list) { - game_list_model = game_list; - if (lobby) { - lobby->UpdateGameList(game_list); - } - if (host_room) { - host_room->UpdateGameList(game_list); - } -} diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h deleted file mode 100644 index d6149838f..000000000 --- a/src/yuzu/multiplayer/state.h +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <QWidget> -#include "network/announce_multiplayer_session.h" -#include "network/network.h" - -class QStandardItemModel; -class Lobby; -class HostRoomWindow; -class ClientRoomWindow; -class DirectConnectWindow; -class ClickableLabel; - -namespace Core { -class System; -} - -class MultiplayerState : public QWidget { - Q_OBJECT; - -public: - enum class NotificationStatus { - Uninitialized, - Disconnected, - Connected, - Notification, - }; - - explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room, - QAction* show_room, Core::System& system_); - ~MultiplayerState(); - - /** - * Close all open multiplayer related dialogs - */ - void Close(); - - void SetNotificationStatus(NotificationStatus state); - - void UpdateNotificationStatus(); - - ClickableLabel* GetStatusText() const { - return status_text; - } - - ClickableLabel* GetStatusIcon() const { - return status_icon; - } - - void retranslateUi(); - - /** - * Whether a public room is being hosted or not. - * When this is true, Web Services configuration should be disabled. - */ - bool IsHostingPublicRoom() const; - - void UpdateCredentials(); - - /** - * Updates the multiplayer dialogs with a new game list model. - * This model should be the original model of the game list. - */ - void UpdateGameList(QStandardItemModel* game_list); - -public slots: - void OnNetworkStateChanged(const Network::RoomMember::State& state); - void OnNetworkError(const Network::RoomMember::Error& error); - void OnViewLobby(); - void OnCreateRoom(); - bool OnCloseRoom(); - void OnOpenNetworkRoom(); - void OnDirectConnectToRoom(); - void OnAnnounceFailed(const WebService::WebResult&); - void OnSaveConfig(); - void UpdateThemedIcons(); - void ShowNotification(); - void HideNotification(); - -signals: - void NetworkStateChanged(const Network::RoomMember::State&); - void NetworkError(const Network::RoomMember::Error&); - void AnnounceFailed(const WebService::WebResult&); - void SaveConfig(); - -private: - Lobby* lobby = nullptr; - HostRoomWindow* host_room = nullptr; - ClientRoomWindow* client_room = nullptr; - DirectConnectWindow* direct_connect = nullptr; - ClickableLabel* status_icon = nullptr; - ClickableLabel* status_text = nullptr; - QStandardItemModel* game_list_model = nullptr; - QAction* leave_room; - QAction* show_room; - std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; - Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized; - NotificationStatus notification_status = NotificationStatus::Uninitialized; - bool has_mod_perms = false; - Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle; - Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle; - - bool show_notification = false; - Core::System& system; - Network::RoomNetwork& room_network; -}; - -Q_DECLARE_METATYPE(WebService::WebResult); diff --git a/src/yuzu/multiplayer/validation.h b/src/yuzu/multiplayer/validation.h deleted file mode 100644 index cbbe6757b..000000000 --- a/src/yuzu/multiplayer/validation.h +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <QRegularExpression> -#include <QString> -#include <QValidator> - -class Validation { -public: - Validation() - : room_name(room_name_regex), nickname(nickname_regex), ip(ip_regex), port(0, UINT16_MAX) {} - - ~Validation() = default; - - const QValidator* GetRoomName() const { - return &room_name; - } - const QValidator* GetNickname() const { - return &nickname; - } - const QValidator* GetIP() const { - return &ip; - } - const QValidator* GetPort() const { - return &port; - } - -private: - /// room name can be alphanumeric and " " "_" "." and "-" and must have a size of 4-20 - QRegularExpression room_name_regex = - QRegularExpression(QStringLiteral("^[a-zA-Z0-9._ -]{4,20}")); - QRegularExpressionValidator room_name; - - /// nickname can be alphanumeric and " " "_" "." and "-" and must have a size of 4-20 - const QRegularExpression nickname_regex = - QRegularExpression(QStringLiteral("^[a-zA-Z0-9._ -]{4,20}")); - QRegularExpressionValidator nickname; - - /// ipv4 / ipv6 / hostnames - QRegularExpression ip_regex = QRegularExpression(QStringLiteral( - // IPv4 regex - "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|" - // IPv6 regex - "^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|" - "(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-" - "5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|" - "(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)" - "(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|" - "(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]" - "\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" - "(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[" - "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" - "(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[" - "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" - "(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[" - "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|" - "(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?" - "\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$|" - // Hostname regex - "^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}$")); - QRegularExpressionValidator ip; - - /// port must be between 0 and 65535 - QIntValidator port; -}; |