From b7c241ea4ccd452253e0fec8741c1ab2cc3c3df4 Mon Sep 17 00:00:00 2001 From: Sleepy Flower Girl Date: Tue, 3 Jul 2018 17:50:08 -0400 Subject: [PATCH 1/3] Add Discord Join Net Play functionally --- Source/Core/DolphinQt/CMakeLists.txt | 1 + Source/Core/DolphinQt/DolphinQt.vcxproj | 3 + Source/Core/DolphinQt/MainWindow.cpp | 11 ++ Source/Core/DolphinQt/MainWindow.h | 2 + .../Core/DolphinQt/NetPlay/NetPlayDialog.cpp | 38 ++++++ Source/Core/DolphinQt2/DiscordHandler.cpp | 45 +++++++ Source/Core/DolphinQt2/DiscordHandler.h | 30 +++++ Source/Core/UICommon/DiscordPresence.cpp | 122 +++++++++++++++++- Source/Core/UICommon/DiscordPresence.h | 16 ++- 9 files changed, 265 insertions(+), 3 deletions(-) create mode 100644 Source/Core/DolphinQt2/DiscordHandler.cpp create mode 100644 Source/Core/DolphinQt2/DiscordHandler.h diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index eff3bdd840..b54d09ebf6 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -8,6 +8,7 @@ set(CMAKE_AUTOMOC ON) add_executable(dolphin-emu AboutDialog.cpp CheatsManager.cpp + DiscordHandler.cpp FIFO/FIFOPlayerWindow.cpp FIFO/FIFOAnalyzer.cpp HotkeyScheduler.cpp diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 0228011308..3e5ad13b14 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -107,6 +107,7 @@ + @@ -175,6 +176,7 @@ + @@ -320,6 +322,7 @@ + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index c77b08718f..4804b469bc 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -67,6 +67,7 @@ #include "DolphinQt/Debugger/MemoryWidget.h" #include "DolphinQt/Debugger/RegisterWidget.h" #include "DolphinQt/Debugger/WatchWidget.h" +#include "DolphinQt/DiscordHandler.h" #include "DolphinQt/FIFO/FIFOPlayerWindow.h" #include "DolphinQt/GCMemcardManager.h" #include "DolphinQt/GameList/GameList.h" @@ -1043,7 +1044,12 @@ void MainWindow::BootWiiSystemMenu() void MainWindow::NetPlayInit() { m_netplay_setup_dialog = new NetPlaySetupDialog(this); +<<<<<<< HEAD:Source/Core/DolphinQt/MainWindow.cpp m_netplay_dialog = new NetPlayDialog; +======= + m_netplay_dialog = new NetPlayDialog(this); + m_netplay_discord = new DiscordHandler(); +>>>>>>> Add Discord Join Net Play functionally:Source/Core/DolphinQt2/MainWindow.cpp connect(m_netplay_dialog, &NetPlayDialog::Boot, this, [this](const QString& path) { StartGame(path); }); @@ -1051,6 +1057,10 @@ void MainWindow::NetPlayInit() connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit); connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin); connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Host, this, &MainWindow::NetPlayHost); + connect(m_netplay_discord, &DiscordHandler::Join, this, &MainWindow::NetPlayJoin); + + Discord::InitNetPlayFunctionality([this] { m_netplay_discord->DiscordJoin(); }); + m_netplay_discord->Start(); } bool MainWindow::NetPlayJoin() @@ -1169,6 +1179,7 @@ void MainWindow::NetPlayQuit() { Settings::Instance().ResetNetPlayClient(); Settings::Instance().ResetNetPlayServer(); + Discord::UpdateDiscordPresence(); } void MainWindow::EnableScreenSaver(bool enable) diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index 8c387a5f20..360e7343de 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -19,6 +19,7 @@ struct BootParameters; class CheatsManager; class CodeWidget; class ControllersWindow; +class DiscordHandler; class DragEnterEvent; class FIFOPlayerWindow; class GameList; @@ -183,6 +184,7 @@ private: ControllersWindow* m_controllers_window; SettingsWindow* m_settings_window; NetPlayDialog* m_netplay_dialog; + DiscordHandler* m_netplay_discord; NetPlaySetupDialog* m_netplay_setup_dialog; GraphicsWindow* m_graphics_window; static constexpr int num_gc_controllers = 4; diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index e5af25d6cf..5224fb4820 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -29,6 +29,7 @@ #include "Common/CommonPaths.h" #include "Common/Config/Config.h" +#include "Common/HttpRequest.h" #include "Common/TraversalClient.h" #include "Core/Config/GraphicsSettings.h" @@ -49,6 +50,7 @@ #include "DolphinQt/Resources.h" #include "DolphinQt/Settings.h" +#include "UICommon/DiscordPresence.h" #include "UICommon/GameFile.h" #include "VideoCommon/VideoConfig.h" @@ -418,6 +420,7 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal) m_nickname = nickname; m_use_traversal = use_traversal; m_buffer_size = 0; + m_old_player_count = 0; m_room_box->clear(); m_chat_edit->clear(); @@ -565,6 +568,41 @@ void NetPlayDialog::UpdateGUI() m_hostcode_action_button->setText(tr("Copy")); m_hostcode_action_button->setEnabled(true); } + + if (m_old_player_count != player_count) + { + if (m_use_traversal) + { + const auto host_id = g_TraversalClient->GetHostID(); + Discord::UpdateDiscordPresence(player_count, Discord::SecretType::RoomID, + std::string(host_id.begin(), host_id.end())); + } + else + { + // Temporary soluation + // To Do: Don't rely on a service that Dolphin devs aren't in control of. Ask one of the + // project managers about this. + + Common::HttpRequest request; + Common::HttpRequest::Response response = request.Get("https://www.myexternalip.com/raw"); + + if (!response.has_value()) + return; + + // The response ends with a /n and the - 1 removes that + std::string exernalIPAddress = std::string(response->begin(), response->end() - 1); + std::string port = std::to_string(Settings::Instance().GetNetPlayServer()->GetPort()); + std::string secret; + secret.reserve(exernalIPAddress.length() + 1 + port.length()); + secret += exernalIPAddress; + secret += ':'; + secret += port; + + Discord::UpdateDiscordPresence(player_count, Discord::SecretType::IPAddress, secret); + } + + m_old_player_count = player_count; + } } // NetPlayUI methods diff --git a/Source/Core/DolphinQt2/DiscordHandler.cpp b/Source/Core/DolphinQt2/DiscordHandler.cpp new file mode 100644 index 0000000000..bad505f2de --- /dev/null +++ b/Source/Core/DolphinQt2/DiscordHandler.cpp @@ -0,0 +1,45 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/DiscordHandler.h" + +#include "Common/Thread.h" + +#include "UICommon/DiscordPresence.h" + +DiscordHandler::DiscordHandler() = default; + +DiscordHandler::~DiscordHandler() +{ + Stop(); +} + +void DiscordHandler::Start() +{ + m_stop_requested.Set(false); + m_thread = std::thread(&DiscordHandler::Run, this); +} + +void DiscordHandler::Stop() +{ + m_stop_requested.Set(true); + + if (m_thread.joinable()) + m_thread.join(); +} + +void DiscordHandler::DiscordJoin() +{ + emit DiscordHandler::Join(); +} + +void DiscordHandler::Run() +{ + while (!m_stop_requested.IsSet()) + { + Common::SleepCurrentThread(1000 * 2); + + Discord::CallPendingCallbacks(); + } +} diff --git a/Source/Core/DolphinQt2/DiscordHandler.h b/Source/Core/DolphinQt2/DiscordHandler.h new file mode 100644 index 0000000000..1cc6787b46 --- /dev/null +++ b/Source/Core/DolphinQt2/DiscordHandler.h @@ -0,0 +1,30 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "Common/Flag.h" + +class DiscordHandler : public QObject +{ + Q_OBJECT +public: + explicit DiscordHandler(); + ~DiscordHandler(); + + void Start(); + void Stop(); + void DiscordJoin(); +signals: + void Join(); + +private: + void Run(); + Common::Flag m_stop_requested; + std::thread m_thread; +}; diff --git a/Source/Core/UICommon/DiscordPresence.cpp b/Source/Core/UICommon/DiscordPresence.cpp index dece60dcbc..afd57d87c7 100644 --- a/Source/Core/UICommon/DiscordPresence.cpp +++ b/Source/Core/UICommon/DiscordPresence.cpp @@ -2,10 +2,14 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include "UICommon/DiscordPresence.h" +#include "Common/Hash.h" + +#include "Core/Config/NetplaySettings.h" #include "Core/Config/UISettings.h" #include "Core/ConfigManager.h" +#include "UICommon/DiscordPresence.h" + #ifdef USE_DISCORD_PRESENCE #include @@ -15,6 +19,61 @@ namespace Discord { +#ifdef USE_DISCORD_PRESENCE +static JoinFunction join_function = nullptr; +static const char* username = ""; + +static void HandleDiscordReady(const DiscordUser* user) +{ + username = user->username; +} + +static void HandleDiscordJoin(const char* join_secret) +{ + if (join_function == nullptr) + return; + + if (Config::Get(Config::NETPLAY_NICKNAME) == Config::NETPLAY_NICKNAME.default_value) + Config::SetBaseOrCurrent(Config::NETPLAY_NICKNAME, username); + + std::string secret(join_secret); + + size_t offset = 0; + std::string type = secret.substr(offset, secret.find('\n')); + offset += type.length() + 1; + + switch (static_cast(std::stol(type))) + { + default: + case SecretType::Empty: + return; + + case SecretType::IPAddress: + { + Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, "direct"); + + std::string host = secret.substr(offset, secret.find_last_of(':') - offset); + Config::SetBaseOrCurrent(Config::NETPLAY_HOST_CODE, host); + + offset += host.length(); + if (secret[offset] == ':') + Config::SetBaseOrCurrent(Config::NETPLAY_CONNECT_PORT, std::stoul(secret.substr(offset + 1))); + } + break; + + case SecretType::RoomID: + { + Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, "traversal"); + + Config::SetBaseOrCurrent(Config::NETPLAY_HOST_CODE, secret.substr(offset)); + } + break; + } + + join_function(); +} +#endif + void Init() { #ifdef USE_DISCORD_PRESENCE @@ -22,13 +81,34 @@ void Init() return; DiscordEventHandlers handlers = {}; + + handlers.ready = HandleDiscordReady; + handlers.joinGame = HandleDiscordJoin; // The number is the client ID for Dolphin, it's used for images and the appication name Discord_Initialize("455712169795780630", &handlers, 1, nullptr); UpdateDiscordPresence(); #endif } -void UpdateDiscordPresence() +void CallPendingCallbacks() +{ +#ifdef USE_DISCORD_PRESENCE + if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE)) + return; + + Discord_RunCallbacks(); + +#endif +} + +void InitNetPlayFunctionality(const JoinFunction& join) +{ +#ifdef USE_DISCORD_PRESENCE + join_function = std::move(join); +#endif +} + +void UpdateDiscordPresence(const int party_size, SecretType type, const std::string& secret) { #ifdef USE_DISCORD_PRESENCE if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE)) @@ -41,6 +121,44 @@ void UpdateDiscordPresence() discord_presence.largeImageText = "Dolphin is an emulator for the GameCube and the Wii."; discord_presence.details = title.empty() ? "Not in-game" : title.c_str(); discord_presence.startTimestamp = std::time(nullptr); + + if (0 < party_size) + { + if (party_size < 4) + { + discord_presence.state = "In a party"; + discord_presence.partySize = party_size; + discord_presence.partyMax = 4; + } + else + { + // others can still join to spectate + discord_presence.state = "In a full party"; + discord_presence.partySize = party_size; + // Note: joining still works without partyMax + } + } + + std::string party_ID; + std::string secret_final; + if (type != SecretType::Empty) + { + // Declearing party_ID or secret_final here will deallocate the variable before passing the + // values over to Discord_UpdatePresence. + + const size_t secret_length = secret.length(); + party_ID = std::to_string( + Common::HashAdler32(reinterpret_cast(secret.c_str()), secret_length)); + + const std::string secret_type = std::to_string(static_cast(type)); + secret_final.reserve(secret_type.length() + 1 + secret_length); + secret_final += secret_type; + secret_final += '\n'; + secret_final += secret; + } + discord_presence.partyId = party_ID.c_str(); + discord_presence.joinSecret = secret_final.c_str(); + Discord_UpdatePresence(&discord_presence); #endif } diff --git a/Source/Core/UICommon/DiscordPresence.h b/Source/Core/UICommon/DiscordPresence.h index 9c4673f885..f2e40bb0b6 100644 --- a/Source/Core/UICommon/DiscordPresence.h +++ b/Source/Core/UICommon/DiscordPresence.h @@ -4,10 +4,24 @@ #pragma once +#include + namespace Discord { +using JoinFunction = std::function; + +enum class SecretType : char +{ + Empty, + IPAddress, + RoomID, +}; + void Init(); -void UpdateDiscordPresence(); +void InitNetPlayFunctionality(const JoinFunction& join); +void CallPendingCallbacks(); +void UpdateDiscordPresence(int party_size = 0, SecretType type = SecretType::Empty, + const std::string& secret = {}); void Shutdown(); void SetDiscordPresenceEnabled(bool enabled); } // namespace Discord From c2aedb7649b09250485f96b85345afeb94a14eca Mon Sep 17 00:00:00 2001 From: Sleepy Flower Girl Date: Fri, 20 Jul 2018 18:27:43 -0400 Subject: [PATCH 2/3] Adds a UI for accepting Discord join requests in Dolphin also did these things fixed crash from joining user that isn't hosting via a direct connection current game stat can now pass to override the current game in config uses ip endpoint from dolphin.org --- Source/Core/Core/NetPlayClient.cpp | 1 + Source/Core/Core/NetPlayClient.h | 1 + Source/Core/Core/NetPlayServer.cpp | 9 +- Source/Core/DolphinQt/CMakeLists.txt | 1 + Source/Core/DolphinQt/DiscordHandler.cpp | 81 +++++++++++++++ Source/Core/DolphinQt/DiscordHandler.h | 43 ++++++++ .../DolphinQt/DiscordJoinRequestDialog.cpp | 90 +++++++++++++++++ .../Core/DolphinQt/DiscordJoinRequestDialog.h | 35 +++++++ Source/Core/DolphinQt/DolphinQt.vcxproj | 3 + Source/Core/DolphinQt/MainWindow.cpp | 11 ++- .../Core/DolphinQt/NetPlay/NetPlayDialog.cpp | 98 +++++++++++++------ Source/Core/DolphinQt/NetPlay/NetPlayDialog.h | 4 + Source/Core/DolphinQt2/DiscordHandler.cpp | 45 --------- Source/Core/DolphinQt2/DiscordHandler.h | 30 ------ Source/Core/UICommon/DiscordPresence.cpp | 53 +++++++--- Source/Core/UICommon/DiscordPresence.h | 18 +++- 16 files changed, 393 insertions(+), 130 deletions(-) create mode 100644 Source/Core/DolphinQt/DiscordHandler.cpp create mode 100644 Source/Core/DolphinQt/DiscordHandler.h create mode 100644 Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp create mode 100644 Source/Core/DolphinQt/DiscordJoinRequestDialog.h delete mode 100644 Source/Core/DolphinQt2/DiscordHandler.cpp delete mode 100644 Source/Core/DolphinQt2/DiscordHandler.h diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 0630fdd625..f9c093db7f 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -1330,6 +1330,7 @@ void NetPlayClient::OnTraversalStateChanged() Disconnect(); m_dialog->OnTraversalError(m_traversal_client->GetFailureReason()); } + m_dialog->OnTraversalStateChanged(state); } // called from ---NETPLAY--- thread diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index ad9d3ee971..2a76dd5cbe 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -47,6 +47,7 @@ public: virtual void OnConnectionLost() = 0; virtual void OnConnectionError(const std::string& message) = 0; virtual void OnTraversalError(TraversalClient::FailureReason error) = 0; + virtual void OnTraversalStateChanged(TraversalClient::State state) = 0; virtual void OnSaveDataSyncFailure() = 0; virtual bool IsRecording() = 0; diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index e49626976c..af4167060f 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -809,8 +809,15 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) void NetPlayServer::OnTraversalStateChanged() { - if (m_dialog && m_traversal_client->GetState() == TraversalClient::Failure) + if (!m_dialog) + return; + + const TraversalClient::State state = m_traversal_client->GetState(); + + if (state == TraversalClient::Failure) m_dialog->OnTraversalError(m_traversal_client->GetFailureReason()); + + m_dialog->OnTraversalStateChanged(state); } // called from ---GUI--- thread diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index b54d09ebf6..86a73c34ed 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable(dolphin-emu AboutDialog.cpp CheatsManager.cpp DiscordHandler.cpp + DiscordJoinRequestDialog.cpp FIFO/FIFOPlayerWindow.cpp FIFO/FIFOAnalyzer.cpp HotkeyScheduler.cpp diff --git a/Source/Core/DolphinQt/DiscordHandler.cpp b/Source/Core/DolphinQt/DiscordHandler.cpp new file mode 100644 index 0000000000..fa3f39a8ad --- /dev/null +++ b/Source/Core/DolphinQt/DiscordHandler.cpp @@ -0,0 +1,81 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#ifdef USE_DISCORD_PRESENCE + +#include + +#include + +#include "DolphinQt/DiscordHandler.h" + +#include "Common/Thread.h" + +#include "UICommon/DiscordPresence.h" + +DiscordHandler::DiscordHandler(QWidget* parent) : QObject{parent}, m_parent{parent} +{ + connect(this, &DiscordHandler::JoinRequest, this, &DiscordHandler::ShowNewJoinRequest); +} + +DiscordHandler::~DiscordHandler() +{ + Stop(); +} + +void DiscordHandler::Start() +{ + m_stop_requested.Set(false); + m_thread = std::thread(&DiscordHandler::Run, this); +} + +void DiscordHandler::Stop() +{ + m_stop_requested.Set(true); + + if (m_thread.joinable()) + m_thread.join(); +} + +void DiscordHandler::DiscordJoinRequest(const char* id, const std::string& discord_tag, + const char* avatar) +{ + m_request_dialogs.emplace_front(m_parent, id, discord_tag, avatar); + emit DiscordHandler::JoinRequest(); +} + +void DiscordHandler::DiscordJoin() +{ + emit DiscordHandler::Join(); +} + +void DiscordHandler::ShowNewJoinRequest() +{ + m_request_dialogs.front().show(); + QApplication::alert(nullptr, DiscordJoinRequestDialog::s_max_lifetime_seconds * 1000); +} + +void DiscordHandler::Run() +{ + while (!m_stop_requested.IsSet()) + { + if (m_thread.joinable()) + Discord::CallPendingCallbacks(); + + // close and remove dead requests + for (auto request_dialog = m_request_dialogs.rbegin(); + request_dialog != m_request_dialogs.rend(); ++request_dialog) + { + if (std::time(nullptr) < request_dialog->GetCloseTimestamp()) + continue; + request_dialog->close(); + std::advance(request_dialog, 1); + m_request_dialogs.erase(request_dialog.base()); + } + + Common::SleepCurrentThread(1000 * 2); + } +} + +#endif diff --git a/Source/Core/DolphinQt/DiscordHandler.h b/Source/Core/DolphinQt/DiscordHandler.h new file mode 100644 index 0000000000..4f951ceadd --- /dev/null +++ b/Source/Core/DolphinQt/DiscordHandler.h @@ -0,0 +1,43 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +// Note using a ifdef around this class causes link issues with qt + +#include +#include + +#include + +#include "Common/Flag.h" + +#include "DolphinQt/DiscordJoinRequestDialog.h" + +#include "UICommon/DiscordPresence.h" + +class DiscordHandler : public QObject, public Discord::Handler +{ + Q_OBJECT +public: + explicit DiscordHandler(QWidget* parent); + ~DiscordHandler(); + + void Start(); + void Stop(); + void DiscordJoin() override; + void DiscordJoinRequest(const char* id, const std::string& discord_tag, + const char* avatar) override; + void ShowNewJoinRequest(); +signals: + void Join(); + void JoinRequest(); + +private: + void Run(); + QWidget* m_parent; + Common::Flag m_stop_requested; + std::thread m_thread; + std::list m_request_dialogs; +}; diff --git a/Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp b/Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp new file mode 100644 index 0000000000..c7177a4a03 --- /dev/null +++ b/Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp @@ -0,0 +1,90 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#ifdef USE_DISCORD_PRESENCE + +#include +#include +#include +#include + +#include + +#include "Common/HttpRequest.h" +#include "Common/StringUtil.h" + +#include "DolphinQt/DiscordJoinRequestDialog.h" + +DiscordJoinRequestDialog::DiscordJoinRequestDialog(QWidget* parent, const char* id, + const std::string& discord_tag, + const char* avatar) + : QDialog(parent), m_user_id(id), m_close_timestamp(std::time(nullptr) + s_max_lifetime_seconds) +{ + setWindowTitle(tr("Request to Join Your Party")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + QPixmap avatar_pixmap; + + if (avatar[0] != '\0') + { + const std::string avatar_endpoint = + StringFromFormat("https://cdn.discordapp.com/avatars/%s/%s.png", id, avatar); + + Common::HttpRequest request; + Common::HttpRequest::Response response = request.Get(avatar_endpoint); + + if (response.has_value()) + avatar_pixmap.loadFromData(response->data(), static_cast(response->size()), "png"); + } + + CreateMainLayout(discord_tag, avatar_pixmap); + ConnectWidgets(); +} + +std::time_t DiscordJoinRequestDialog::GetCloseTimestamp() const +{ + return m_close_timestamp; +} + +void DiscordJoinRequestDialog::CreateMainLayout(const std::string& discord_tag, + const QPixmap& avatar) +{ + m_main_layout = new QGridLayout; + + m_invite_button = new QPushButton(QString::fromWCharArray(L"\u2714 Invite")); + m_decline_button = new QPushButton(QString::fromWCharArray(L"\u2716 Decline")); + m_ignore_button = new QPushButton(tr("Ignore")); + + if (!avatar.isNull()) + { + QLabel* picture = new QLabel(); + picture->setPixmap(avatar); + m_main_layout->addWidget(picture, 1, 0, 1, 3, Qt::AlignHCenter); + } + + m_main_layout->addWidget(new QLabel(tr(discord_tag.c_str())), 2, 0, 3, 3, Qt::AlignHCenter); + m_main_layout->addWidget(new QLabel(tr("wants to join your party.")), 4, 0, 4, 3, + Qt::AlignHCenter); + m_main_layout->addWidget(m_invite_button, 8, 0); + m_main_layout->addWidget(m_decline_button, 8, 1); + m_main_layout->addWidget(m_ignore_button, 8, 2); + + setLayout(m_main_layout); +} + +void DiscordJoinRequestDialog::ConnectWidgets() +{ + connect(m_invite_button, &QPushButton::clicked, [this] { Reply(DISCORD_REPLY_YES); }); + connect(m_decline_button, &QPushButton::clicked, [this] { Reply(DISCORD_REPLY_NO); }); + connect(m_ignore_button, &QPushButton::clicked, [this] { Reply(DISCORD_REPLY_IGNORE); }); + connect(this, &QDialog::rejected, this, [this] { Reply(DISCORD_REPLY_IGNORE); }); +} + +void DiscordJoinRequestDialog::Reply(int reply) +{ + Discord_Respond(m_user_id, reply); + close(); +} + +#endif diff --git a/Source/Core/DolphinQt/DiscordJoinRequestDialog.h b/Source/Core/DolphinQt/DiscordJoinRequestDialog.h new file mode 100644 index 0000000000..d2345cf407 --- /dev/null +++ b/Source/Core/DolphinQt/DiscordJoinRequestDialog.h @@ -0,0 +1,35 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +class QGridLayout; +class QPixmap; + +class DiscordJoinRequestDialog : public QDialog +{ + Q_OBJECT +public: + explicit DiscordJoinRequestDialog(QWidget* parent, const char* id, const std::string& discord_tag, + const char* avatar); + std::time_t GetCloseTimestamp() const; + + static constexpr std::time_t s_max_lifetime_seconds = 30; + +private: + void CreateMainLayout(const std::string& discord_tag, const QPixmap& avatar); + void ConnectWidgets(); + void Reply(int reply); + + QGridLayout* m_main_layout; + QPushButton* m_invite_button; + QPushButton* m_decline_button; + QPushButton* m_ignore_button; + + const char* const m_user_id; + const std::time_t m_close_timestamp; +}; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 3e5ad13b14..216c0aa2f5 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -108,6 +108,7 @@ + @@ -177,6 +178,7 @@ + @@ -323,6 +325,7 @@ + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 4804b469bc..7f016e5993 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1044,12 +1044,11 @@ void MainWindow::BootWiiSystemMenu() void MainWindow::NetPlayInit() { m_netplay_setup_dialog = new NetPlaySetupDialog(this); -<<<<<<< HEAD:Source/Core/DolphinQt/MainWindow.cpp m_netplay_dialog = new NetPlayDialog; -======= m_netplay_dialog = new NetPlayDialog(this); - m_netplay_discord = new DiscordHandler(); ->>>>>>> Add Discord Join Net Play functionally:Source/Core/DolphinQt2/MainWindow.cpp +#ifdef USE_DISCORD_PRESENCE + m_netplay_discord = new DiscordHandler(this); +#endif connect(m_netplay_dialog, &NetPlayDialog::Boot, this, [this](const QString& path) { StartGame(path); }); @@ -1057,10 +1056,12 @@ void MainWindow::NetPlayInit() connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit); connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin); connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Host, this, &MainWindow::NetPlayHost); +#ifdef USE_DISCORD_PRESENCE connect(m_netplay_discord, &DiscordHandler::Join, this, &MainWindow::NetPlayJoin); - Discord::InitNetPlayFunctionality([this] { m_netplay_discord->DiscordJoin(); }); + Discord::InitNetPlayFunctionality(*m_netplay_discord); m_netplay_discord->Start(); +#endif } bool MainWindow::NetPlayJoin() diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index 5224fb4820..1e18eaea87 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -34,6 +34,7 @@ #include "Core/Config/GraphicsSettings.h" #include "Core/Config/MainSettings.h" +#include "Core/Config/NetplaySettings.h" #include "Core/Config/SYSCONFSettings.h" #include "Core/ConfigLoaders/GameConfigLoader.h" #include "Core/ConfigManager.h" @@ -461,6 +462,55 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal) UpdateGUI(); } +void NetPlayDialog::UpdateDiscordPresence() +{ +#ifdef USE_DISCORD_PRESENCE + // both m_current_game and m_player_count need to be set for the status to be displayed correctly + if (m_player_count == 0 || m_current_game.empty()) + return; + + const auto use_default = [this]() { + Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::Empty, "", m_current_game); + }; + + if (g_TraversalClient) + { + const auto host_id = g_TraversalClient->GetHostID(); + if (host_id == decltype(host_id)()) + return use_default(); + + Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::RoomID, + std::string(host_id.begin(), host_id.end()), m_current_game); + } + else if (IsHosting()) + { + if (m_exernal_ip_address.empty()) + { + Common::HttpRequest request; + Common::HttpRequest::Response response = + request.Get("https://ip.dolphin-emu.org/", {{"X-Is-Dolphin", "1"}}); + + if (!response.has_value()) + return use_default(); + m_exernal_ip_address = std::string(response->begin(), response->end()); + } + const int port = Settings::Instance().GetNetPlayServer()->GetPort(); + + Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::IPAddress, + Discord::CreateSecretFromIPAddress(m_exernal_ip_address, port), + m_current_game); + } + else + { + Discord::UpdateDiscordPresence( + m_player_count, Discord::SecretType::IPAddress, + Discord::CreateSecretFromIPAddress(Config::Get(Config::NETPLAY_HOST_CODE), + Config::Get(Config::NETPLAY_HOST_PORT)), + m_current_game); + } +#endif +} + void NetPlayDialog::UpdateGUI() { auto client = Settings::Instance().GetNetPlayClient(); @@ -569,39 +619,10 @@ void NetPlayDialog::UpdateGUI() m_hostcode_action_button->setEnabled(true); } - if (m_old_player_count != player_count) + if (m_old_player_count != m_player_count) { - if (m_use_traversal) - { - const auto host_id = g_TraversalClient->GetHostID(); - Discord::UpdateDiscordPresence(player_count, Discord::SecretType::RoomID, - std::string(host_id.begin(), host_id.end())); - } - else - { - // Temporary soluation - // To Do: Don't rely on a service that Dolphin devs aren't in control of. Ask one of the - // project managers about this. - - Common::HttpRequest request; - Common::HttpRequest::Response response = request.Get("https://www.myexternalip.com/raw"); - - if (!response.has_value()) - return; - - // The response ends with a /n and the - 1 removes that - std::string exernalIPAddress = std::string(response->begin(), response->end() - 1); - std::string port = std::to_string(Settings::Instance().GetNetPlayServer()->GetPort()); - std::string secret; - secret.reserve(exernalIPAddress.length() + 1 + port.length()); - secret += exernalIPAddress; - secret += ':'; - secret += port; - - Discord::UpdateDiscordPresence(player_count, Discord::SecretType::IPAddress, secret); - } - - m_old_player_count = player_count; + UpdateDiscordPresence(); + m_old_player_count = m_player_count; } } @@ -670,6 +691,7 @@ void NetPlayDialog::OnMsgChangeGame(const std::string& title) QueueOnObject(this, [this, qtitle, title] { m_game_button->setText(qtitle); m_current_game = title; + UpdateDiscordPresence(); }); DisplayMessage(tr("Game changed to \"%1\"").arg(qtitle), "magenta"); } @@ -765,6 +787,18 @@ void NetPlayDialog::OnTraversalError(TraversalClient::FailureReason error) }); } +void NetPlayDialog::OnTraversalStateChanged(TraversalClient::State state) +{ + switch (state) + { + case TraversalClient::State::Connected: + case TraversalClient::State::Failure: + UpdateDiscordPresence(); + default: + break; + } +} + void NetPlayDialog::OnSaveDataSyncFailure() { QueueOnObject(this, [this] { SetOptionsEnabled(true); }); diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h index f8f9926101..acb9d27596 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h @@ -51,6 +51,7 @@ public: void OnConnectionLost() override; void OnConnectionError(const std::string& message) override; void OnTraversalError(TraversalClient::FailureReason error) override; + void OnTraversalStateChanged(TraversalClient::State state) override; void OnSaveDataSyncFailure() override; bool IsRecording() override; @@ -73,6 +74,7 @@ private: void OnStart(); void DisplayMessage(const QString& msg, const std::string& color, int duration = OSD::Duration::NORMAL); + void UpdateDiscordPresence(); void UpdateGUI(); void GameStatusChanged(bool running); void SetOptionsEnabled(bool enabled); @@ -113,6 +115,7 @@ private: MD5Dialog* m_md5_dialog; PadMappingDialog* m_pad_mapping; std::string m_current_game; + std::string m_exernal_ip_address; std::string m_nickname; GameListModel* m_game_list_model = nullptr; bool m_use_traversal = false; @@ -120,4 +123,5 @@ private: bool m_got_stop_request = true; int m_buffer_size = 0; int m_player_count = 0; + int m_old_player_count = 0; }; diff --git a/Source/Core/DolphinQt2/DiscordHandler.cpp b/Source/Core/DolphinQt2/DiscordHandler.cpp deleted file mode 100644 index bad505f2de..0000000000 --- a/Source/Core/DolphinQt2/DiscordHandler.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "DolphinQt2/DiscordHandler.h" - -#include "Common/Thread.h" - -#include "UICommon/DiscordPresence.h" - -DiscordHandler::DiscordHandler() = default; - -DiscordHandler::~DiscordHandler() -{ - Stop(); -} - -void DiscordHandler::Start() -{ - m_stop_requested.Set(false); - m_thread = std::thread(&DiscordHandler::Run, this); -} - -void DiscordHandler::Stop() -{ - m_stop_requested.Set(true); - - if (m_thread.joinable()) - m_thread.join(); -} - -void DiscordHandler::DiscordJoin() -{ - emit DiscordHandler::Join(); -} - -void DiscordHandler::Run() -{ - while (!m_stop_requested.IsSet()) - { - Common::SleepCurrentThread(1000 * 2); - - Discord::CallPendingCallbacks(); - } -} diff --git a/Source/Core/DolphinQt2/DiscordHandler.h b/Source/Core/DolphinQt2/DiscordHandler.h deleted file mode 100644 index 1cc6787b46..0000000000 --- a/Source/Core/DolphinQt2/DiscordHandler.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include - -#include - -#include "Common/Flag.h" - -class DiscordHandler : public QObject -{ - Q_OBJECT -public: - explicit DiscordHandler(); - ~DiscordHandler(); - - void Start(); - void Stop(); - void DiscordJoin(); -signals: - void Join(); - -private: - void Run(); - Common::Flag m_stop_requested; - std::thread m_thread; -}; diff --git a/Source/Core/UICommon/DiscordPresence.cpp b/Source/Core/UICommon/DiscordPresence.cpp index afd57d87c7..6f742308c0 100644 --- a/Source/Core/UICommon/DiscordPresence.cpp +++ b/Source/Core/UICommon/DiscordPresence.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include "Common/Hash.h" +#include "Common/StringUtil.h" #include "Core/Config/NetplaySettings.h" #include "Core/Config/UISettings.h" @@ -20,7 +21,7 @@ namespace Discord { #ifdef USE_DISCORD_PRESENCE -static JoinFunction join_function = nullptr; +static Handler* event_handler = nullptr; static const char* username = ""; static void HandleDiscordReady(const DiscordUser* user) @@ -28,9 +29,18 @@ static void HandleDiscordReady(const DiscordUser* user) username = user->username; } +static void HandleDiscordJoinRequest(const DiscordUser* user) +{ + if (event_handler == nullptr) + return; + + const std::string discord_tag = StringFromFormat("%s#%s", user->username, user->discriminator); + event_handler->DiscordJoinRequest(user->userId, discord_tag, user->avatar); +} + static void HandleDiscordJoin(const char* join_secret) { - if (join_function == nullptr) + if (event_handler == nullptr) return; if (Config::Get(Config::NETPLAY_NICKNAME) == Config::NETPLAY_NICKNAME.default_value) @@ -38,9 +48,8 @@ static void HandleDiscordJoin(const char* join_secret) std::string secret(join_secret); - size_t offset = 0; - std::string type = secret.substr(offset, secret.find('\n')); - offset += type.length() + 1; + std::string type = secret.substr(0, secret.find('\n')); + size_t offset = type.length() + 1; switch (static_cast(std::stol(type))) { @@ -70,10 +79,12 @@ static void HandleDiscordJoin(const char* join_secret) break; } - join_function(); + event_handler->DiscordJoin(); } #endif +Discord::Handler::~Handler() = default; + void Init() { #ifdef USE_DISCORD_PRESENCE @@ -83,6 +94,7 @@ void Init() DiscordEventHandlers handlers = {}; handlers.ready = HandleDiscordReady; + handlers.joinRequest = HandleDiscordJoinRequest; handlers.joinGame = HandleDiscordJoin; // The number is the client ID for Dolphin, it's used for images and the appication name Discord_Initialize("455712169795780630", &handlers, 1, nullptr); @@ -101,20 +113,22 @@ void CallPendingCallbacks() #endif } -void InitNetPlayFunctionality(const JoinFunction& join) +void InitNetPlayFunctionality(Handler& handler) { #ifdef USE_DISCORD_PRESENCE - join_function = std::move(join); + event_handler = &handler; #endif } -void UpdateDiscordPresence(const int party_size, SecretType type, const std::string& secret) +void UpdateDiscordPresence(const int party_size, SecretType type, const std::string& secret, + const std::string& current_game) { #ifdef USE_DISCORD_PRESENCE if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE)) return; - const std::string& title = SConfig::GetInstance().GetTitleDescription(); + const std::string& title = + current_game.empty() ? SConfig::GetInstance().GetTitleDescription() : current_game; DiscordRichPresence discord_presence = {}; discord_presence.largeImageKey = "dolphin_logo"; @@ -139,15 +153,15 @@ void UpdateDiscordPresence(const int party_size, SecretType type, const std::str } } - std::string party_ID; + std::string party_id; std::string secret_final; if (type != SecretType::Empty) { - // Declearing party_ID or secret_final here will deallocate the variable before passing the + // Declearing party_id or secret_final here will deallocate the variable before passing the // values over to Discord_UpdatePresence. const size_t secret_length = secret.length(); - party_ID = std::to_string( + party_id = std::to_string( Common::HashAdler32(reinterpret_cast(secret.c_str()), secret_length)); const std::string secret_type = std::to_string(static_cast(type)); @@ -156,13 +170,24 @@ void UpdateDiscordPresence(const int party_size, SecretType type, const std::str secret_final += '\n'; secret_final += secret; } - discord_presence.partyId = party_ID.c_str(); + discord_presence.partyId = party_id.c_str(); discord_presence.joinSecret = secret_final.c_str(); Discord_UpdatePresence(&discord_presence); #endif } +std::string CreateSecretFromIPAddress(const std::string& ip_address, int port) +{ + const std::string port_string = std::to_string(port); + std::string secret; + secret.reserve(ip_address.length() + 1 + port_string.length()); + secret += ip_address; + secret += ':'; + secret += port_string; + return secret; +} + void Shutdown() { #ifdef USE_DISCORD_PRESENCE diff --git a/Source/Core/UICommon/DiscordPresence.h b/Source/Core/UICommon/DiscordPresence.h index f2e40bb0b6..c7e921e804 100644 --- a/Source/Core/UICommon/DiscordPresence.h +++ b/Source/Core/UICommon/DiscordPresence.h @@ -9,8 +9,19 @@ namespace Discord { using JoinFunction = std::function; +using JoinRequestFunction = + std::function; -enum class SecretType : char +class Handler +{ +public: + virtual ~Handler(); + virtual void DiscordJoin() = 0; + virtual void DiscordJoinRequest(const char* id, const std::string& discord_tag, + const char* avatar) = 0; +}; + +enum class SecretType { Empty, IPAddress, @@ -18,10 +29,11 @@ enum class SecretType : char }; void Init(); -void InitNetPlayFunctionality(const JoinFunction& join); +void InitNetPlayFunctionality(Handler& handler); void CallPendingCallbacks(); void UpdateDiscordPresence(int party_size = 0, SecretType type = SecretType::Empty, - const std::string& secret = {}); + const std::string& secret = {}, const std::string& current_game = {}); +std::string CreateSecretFromIPAddress(const std::string& ip_address, int port); void Shutdown(); void SetDiscordPresenceEnabled(bool enabled); } // namespace Discord From 158c0d54b1b4f332ab8fca897a8396bde5686e74 Mon Sep 17 00:00:00 2001 From: Sleepy Flower Girl Date: Mon, 6 Aug 2018 17:56:40 -0400 Subject: [PATCH 3/3] Force IPv4 on external IP addresses --- Source/Core/Common/HttpRequest.cpp | 11 ++++ Source/Core/Common/HttpRequest.h | 1 + Source/Core/DolphinQt/DiscordHandler.cpp | 43 +++++++++----- Source/Core/DolphinQt/DiscordHandler.h | 18 ++++-- .../DolphinQt/DiscordJoinRequestDialog.cpp | 39 +++++++------ .../Core/DolphinQt/DiscordJoinRequestDialog.h | 8 +-- Source/Core/DolphinQt/MainWindow.cpp | 9 ++- .../Core/DolphinQt/NetPlay/NetPlayDialog.cpp | 58 ++++++++++--------- Source/Core/DolphinQt/NetPlay/NetPlayDialog.h | 2 +- Source/Core/UICommon/DiscordPresence.cpp | 21 +++---- Source/Core/UICommon/DiscordPresence.h | 5 +- 11 files changed, 129 insertions(+), 86 deletions(-) diff --git a/Source/Core/Common/HttpRequest.cpp b/Source/Core/Common/HttpRequest.cpp index af8eb5f5c5..63e4e85878 100644 --- a/Source/Core/Common/HttpRequest.cpp +++ b/Source/Core/Common/HttpRequest.cpp @@ -29,6 +29,7 @@ public: bool IsValid() const; void SetCookies(const std::string& cookies); + void UseIPv4(); Response Fetch(const std::string& url, Method method, const Headers& headers, const u8* payload, size_t size); @@ -62,6 +63,11 @@ void HttpRequest::SetCookies(const std::string& cookies) m_impl->SetCookies(cookies); } +void HttpRequest::UseIPv4() +{ + m_impl->UseIPv4(); +} + HttpRequest::Response HttpRequest::Get(const std::string& url, const Headers& headers) { return m_impl->Fetch(url, Impl::Method::GET, headers, nullptr, 0); @@ -136,6 +142,11 @@ void HttpRequest::Impl::SetCookies(const std::string& cookies) curl_easy_setopt(m_curl.get(), CURLOPT_COOKIE, cookies.c_str()); } +void HttpRequest::Impl::UseIPv4() +{ + curl_easy_setopt(m_curl.get(), CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); +} + static size_t CurlWriteCallback(char* data, size_t size, size_t nmemb, void* userdata) { auto* buffer = static_cast*>(userdata); diff --git a/Source/Core/Common/HttpRequest.h b/Source/Core/Common/HttpRequest.h index fefb54c784..94916f5c3c 100644 --- a/Source/Core/Common/HttpRequest.h +++ b/Source/Core/Common/HttpRequest.h @@ -32,6 +32,7 @@ public: using Headers = std::map>; void SetCookies(const std::string& cookies); + void UseIPv4(); Response Get(const std::string& url, const Headers& headers = {}); Response Post(const std::string& url, const std::vector& payload, const Headers& headers = {}); diff --git a/Source/Core/DolphinQt/DiscordHandler.cpp b/Source/Core/DolphinQt/DiscordHandler.cpp index fa3f39a8ad..8bfadd21dd 100644 --- a/Source/Core/DolphinQt/DiscordHandler.cpp +++ b/Source/Core/DolphinQt/DiscordHandler.cpp @@ -4,16 +4,19 @@ #ifdef USE_DISCORD_PRESENCE +#include "DolphinQt/DiscordHandler.h" + #include #include -#include "DolphinQt/DiscordHandler.h" - #include "Common/Thread.h" #include "UICommon/DiscordPresence.h" +#include "DolphinQt/DiscordJoinRequestDialog.h" +#include "DolphinQt/QtUtils/RunOnObject.h" + DiscordHandler::DiscordHandler(QWidget* parent) : QObject{parent}, m_parent{parent} { connect(this, &DiscordHandler::JoinRequest, this, &DiscordHandler::ShowNewJoinRequest); @@ -41,8 +44,7 @@ void DiscordHandler::Stop() void DiscordHandler::DiscordJoinRequest(const char* id, const std::string& discord_tag, const char* avatar) { - m_request_dialogs.emplace_front(m_parent, id, discord_tag, avatar); - emit DiscordHandler::JoinRequest(); + emit DiscordHandler::JoinRequest(id, discord_tag, avatar); } void DiscordHandler::DiscordJoin() @@ -50,9 +52,15 @@ void DiscordHandler::DiscordJoin() emit DiscordHandler::Join(); } -void DiscordHandler::ShowNewJoinRequest() +void DiscordHandler::ShowNewJoinRequest(const std::string& id, const std::string& discord_tag, + const std::string& avatar) { - m_request_dialogs.front().show(); + std::lock_guard lock(m_request_dialogs_mutex); + m_request_dialogs.emplace_front(m_parent, id, discord_tag, avatar); + DiscordJoinRequestDialog& request_dialog = m_request_dialogs.front(); + request_dialog.show(); + request_dialog.raise(); + request_dialog.activateWindow(); QApplication::alert(nullptr, DiscordJoinRequestDialog::s_max_lifetime_seconds * 1000); } @@ -64,14 +72,23 @@ void DiscordHandler::Run() Discord::CallPendingCallbacks(); // close and remove dead requests - for (auto request_dialog = m_request_dialogs.rbegin(); - request_dialog != m_request_dialogs.rend(); ++request_dialog) { - if (std::time(nullptr) < request_dialog->GetCloseTimestamp()) - continue; - request_dialog->close(); - std::advance(request_dialog, 1); - m_request_dialogs.erase(request_dialog.base()); + std::lock_guard lock(m_request_dialogs_mutex); + for (auto request_dialog = m_request_dialogs.begin(); + request_dialog != m_request_dialogs.end();) + { + if (std::time(nullptr) < request_dialog->GetCloseTimestamp()) + { + ++request_dialog; + continue; + } + + RunOnObject(m_parent, [this, &request_dialog] { + request_dialog->close(); + request_dialog = m_request_dialogs.erase(request_dialog); + return nullptr; + }); + } } Common::SleepCurrentThread(1000 * 2); diff --git a/Source/Core/DolphinQt/DiscordHandler.h b/Source/Core/DolphinQt/DiscordHandler.h index 4f951ceadd..d1be3d01c0 100644 --- a/Source/Core/DolphinQt/DiscordHandler.h +++ b/Source/Core/DolphinQt/DiscordHandler.h @@ -4,22 +4,22 @@ #pragma once -// Note using a ifdef around this class causes link issues with qt - #include +#include #include #include #include "Common/Flag.h" -#include "DolphinQt/DiscordJoinRequestDialog.h" - #include "UICommon/DiscordPresence.h" +class DiscordJoinRequestDialog; + class DiscordHandler : public QObject, public Discord::Handler { Q_OBJECT +#ifdef USE_DISCORD_PRESENCE public: explicit DiscordHandler(QWidget* parent); ~DiscordHandler(); @@ -29,15 +29,21 @@ public: void DiscordJoin() override; void DiscordJoinRequest(const char* id, const std::string& discord_tag, const char* avatar) override; - void ShowNewJoinRequest(); + void ShowNewJoinRequest(const std::string& id, const std::string& discord_tag, + const std::string& avatar); +#endif + signals: void Join(); - void JoinRequest(); + void JoinRequest(const std::string id, const std::string discord_tag, const std::string avatar); +#ifdef USE_DISCORD_PRESENCE private: void Run(); QWidget* m_parent; Common::Flag m_stop_requested; std::thread m_thread; std::list m_request_dialogs; + std::mutex m_request_dialogs_mutex; +#endif }; diff --git a/Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp b/Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp index c7177a4a03..dd0a6eb64b 100644 --- a/Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp +++ b/Source/Core/DolphinQt/DiscordJoinRequestDialog.cpp @@ -4,6 +4,8 @@ #ifdef USE_DISCORD_PRESENCE +#include "DolphinQt/DiscordJoinRequestDialog.h" + #include #include #include @@ -14,11 +16,9 @@ #include "Common/HttpRequest.h" #include "Common/StringUtil.h" -#include "DolphinQt/DiscordJoinRequestDialog.h" - -DiscordJoinRequestDialog::DiscordJoinRequestDialog(QWidget* parent, const char* id, +DiscordJoinRequestDialog::DiscordJoinRequestDialog(QWidget* parent, const std::string& id, const std::string& discord_tag, - const char* avatar) + const std::string& avatar) : QDialog(parent), m_user_id(id), m_close_timestamp(std::time(nullptr) + s_max_lifetime_seconds) { setWindowTitle(tr("Request to Join Your Party")); @@ -26,10 +26,10 @@ DiscordJoinRequestDialog::DiscordJoinRequestDialog(QWidget* parent, const char* QPixmap avatar_pixmap; - if (avatar[0] != '\0') + if (!avatar.empty()) { - const std::string avatar_endpoint = - StringFromFormat("https://cdn.discordapp.com/avatars/%s/%s.png", id, avatar); + const std::string avatar_endpoint = StringFromFormat( + "https://cdn.discordapp.com/avatars/%s/%s.png", id.c_str(), avatar.c_str()); Common::HttpRequest request; Common::HttpRequest::Response response = request.Get(avatar_endpoint); @@ -38,7 +38,7 @@ DiscordJoinRequestDialog::DiscordJoinRequestDialog(QWidget* parent, const char* avatar_pixmap.loadFromData(response->data(), static_cast(response->size()), "png"); } - CreateMainLayout(discord_tag, avatar_pixmap); + CreateLayout(discord_tag, avatar_pixmap); ConnectWidgets(); } @@ -47,15 +47,18 @@ std::time_t DiscordJoinRequestDialog::GetCloseTimestamp() const return m_close_timestamp; } -void DiscordJoinRequestDialog::CreateMainLayout(const std::string& discord_tag, - const QPixmap& avatar) +void DiscordJoinRequestDialog::CreateLayout(const std::string& discord_tag, const QPixmap& avatar) { m_main_layout = new QGridLayout; - m_invite_button = new QPushButton(QString::fromWCharArray(L"\u2714 Invite")); - m_decline_button = new QPushButton(QString::fromWCharArray(L"\u2716 Decline")); + m_invite_button = new QPushButton(tr("\u2714 Invite")); + m_decline_button = new QPushButton(tr("\u2716 Decline")); m_ignore_button = new QPushButton(tr("Ignore")); + QLabel* text = + new QLabel(tr("%1\nwants to join your party.").arg(QString::fromStdString(discord_tag))); + text->setAlignment(Qt::AlignCenter); + if (!avatar.isNull()) { QLabel* picture = new QLabel(); @@ -63,9 +66,7 @@ void DiscordJoinRequestDialog::CreateMainLayout(const std::string& discord_tag, m_main_layout->addWidget(picture, 1, 0, 1, 3, Qt::AlignHCenter); } - m_main_layout->addWidget(new QLabel(tr(discord_tag.c_str())), 2, 0, 3, 3, Qt::AlignHCenter); - m_main_layout->addWidget(new QLabel(tr("wants to join your party.")), 4, 0, 4, 3, - Qt::AlignHCenter); + m_main_layout->addWidget(text, 2, 0, 3, 3, Qt::AlignHCenter); m_main_layout->addWidget(m_invite_button, 8, 0); m_main_layout->addWidget(m_decline_button, 8, 1); m_main_layout->addWidget(m_ignore_button, 8, 2); @@ -75,15 +76,15 @@ void DiscordJoinRequestDialog::CreateMainLayout(const std::string& discord_tag, void DiscordJoinRequestDialog::ConnectWidgets() { - connect(m_invite_button, &QPushButton::clicked, [this] { Reply(DISCORD_REPLY_YES); }); - connect(m_decline_button, &QPushButton::clicked, [this] { Reply(DISCORD_REPLY_NO); }); - connect(m_ignore_button, &QPushButton::clicked, [this] { Reply(DISCORD_REPLY_IGNORE); }); + connect(m_invite_button, &QPushButton::pressed, [this] { Reply(DISCORD_REPLY_YES); }); + connect(m_decline_button, &QPushButton::pressed, [this] { Reply(DISCORD_REPLY_NO); }); + connect(m_ignore_button, &QPushButton::pressed, [this] { Reply(DISCORD_REPLY_IGNORE); }); connect(this, &QDialog::rejected, this, [this] { Reply(DISCORD_REPLY_IGNORE); }); } void DiscordJoinRequestDialog::Reply(int reply) { - Discord_Respond(m_user_id, reply); + Discord_Respond(m_user_id.c_str(), reply); close(); } diff --git a/Source/Core/DolphinQt/DiscordJoinRequestDialog.h b/Source/Core/DolphinQt/DiscordJoinRequestDialog.h index d2345cf407..d9a7a8c9dd 100644 --- a/Source/Core/DolphinQt/DiscordJoinRequestDialog.h +++ b/Source/Core/DolphinQt/DiscordJoinRequestDialog.h @@ -14,14 +14,14 @@ class DiscordJoinRequestDialog : public QDialog { Q_OBJECT public: - explicit DiscordJoinRequestDialog(QWidget* parent, const char* id, const std::string& discord_tag, - const char* avatar); + explicit DiscordJoinRequestDialog(QWidget* parent, const std::string& id, + const std::string& discord_tag, const std::string& avatar); std::time_t GetCloseTimestamp() const; static constexpr std::time_t s_max_lifetime_seconds = 30; private: - void CreateMainLayout(const std::string& discord_tag, const QPixmap& avatar); + void CreateLayout(const std::string& discord_tag, const QPixmap& avatar); void ConnectWidgets(); void Reply(int reply); @@ -30,6 +30,6 @@ private: QPushButton* m_decline_button; QPushButton* m_ignore_button; - const char* const m_user_id; + const std::string m_user_id; const std::time_t m_close_timestamp; }; diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 7f016e5993..514308e548 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -640,7 +640,8 @@ void MainWindow::OnStopComplete() HideRenderWidget(); EnableScreenSaver(true); #ifdef USE_DISCORD_PRESENCE - Discord::UpdateDiscordPresence(); + if (!m_netplay_dialog->isVisible()) + Discord::UpdateDiscordPresence(); #endif SetFullScreenResolution(false); @@ -794,7 +795,8 @@ void MainWindow::StartGame(std::unique_ptr&& parameters) ShowRenderWidget(); #ifdef USE_DISCORD_PRESENCE - Discord::UpdateDiscordPresence(); + if (!NetPlay::IsNetPlayRunning()) + Discord::UpdateDiscordPresence(); #endif if (SConfig::GetInstance().bFullscreen) @@ -1045,7 +1047,6 @@ void MainWindow::NetPlayInit() { m_netplay_setup_dialog = new NetPlaySetupDialog(this); m_netplay_dialog = new NetPlayDialog; - m_netplay_dialog = new NetPlayDialog(this); #ifdef USE_DISCORD_PRESENCE m_netplay_discord = new DiscordHandler(this); #endif @@ -1180,7 +1181,9 @@ void MainWindow::NetPlayQuit() { Settings::Instance().ResetNetPlayClient(); Settings::Instance().ResetNetPlayServer(); +#ifdef USE_DISCORD_PRESENCE Discord::UpdateDiscordPresence(); +#endif } void MainWindow::EnableScreenSaver(bool enable) diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index 1e18eaea87..a6775180de 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -473,40 +473,44 @@ void NetPlayDialog::UpdateDiscordPresence() Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::Empty, "", m_current_game); }; - if (g_TraversalClient) - { - const auto host_id = g_TraversalClient->GetHostID(); - if (host_id == decltype(host_id)()) - return use_default(); + if (Core::IsRunning()) + return use_default(); - Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::RoomID, - std::string(host_id.begin(), host_id.end()), m_current_game); - } - else if (IsHosting()) + if (IsHosting()) { - if (m_exernal_ip_address.empty()) + if (g_TraversalClient) { - Common::HttpRequest request; - Common::HttpRequest::Response response = - request.Get("https://ip.dolphin-emu.org/", {{"X-Is-Dolphin", "1"}}); - - if (!response.has_value()) + const auto host_id = g_TraversalClient->GetHostID(); + if (host_id[0] == '\0') return use_default(); - m_exernal_ip_address = std::string(response->begin(), response->end()); - } - const int port = Settings::Instance().GetNetPlayServer()->GetPort(); - Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::IPAddress, - Discord::CreateSecretFromIPAddress(m_exernal_ip_address, port), - m_current_game); + Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::RoomID, + std::string(host_id.begin(), host_id.end()), m_current_game); + } + else + { + if (m_external_ip_address.empty()) + { + Common::HttpRequest request; + // ENet does not support IPv6, so IPv4 has to be used + request.UseIPv4(); + Common::HttpRequest::Response response = + request.Get("https://ip.dolphin-emu.org/", {{"X-Is-Dolphin", "1"}}); + + if (!response.has_value()) + return use_default(); + m_external_ip_address = std::string(response->begin(), response->end()); + } + const int port = Settings::Instance().GetNetPlayServer()->GetPort(); + + Discord::UpdateDiscordPresence( + m_player_count, Discord::SecretType::IPAddress, + Discord::CreateSecretFromIPAddress(m_external_ip_address, port), m_current_game); + } } else { - Discord::UpdateDiscordPresence( - m_player_count, Discord::SecretType::IPAddress, - Discord::CreateSecretFromIPAddress(Config::Get(Config::NETPLAY_HOST_CODE), - Config::Get(Config::NETPLAY_HOST_PORT)), - m_current_game); + use_default(); } #endif } @@ -729,11 +733,13 @@ void NetPlayDialog::OnMsgStartGame() auto client = Settings::Instance().GetNetPlayClient(); if (client) client->StartGame(FindGame(m_current_game)); + UpdateDiscordPresence(); }); } void NetPlayDialog::OnMsgStopGame() { + QueueOnObject(this, [this] { UpdateDiscordPresence(); }); } void NetPlayDialog::OnPadBufferChanged(u32 buffer) diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h index acb9d27596..a9d835baf7 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h @@ -115,7 +115,7 @@ private: MD5Dialog* m_md5_dialog; PadMappingDialog* m_pad_mapping; std::string m_current_game; - std::string m_exernal_ip_address; + std::string m_external_ip_address; std::string m_nickname; GameListModel* m_game_list_model = nullptr; bool m_use_traversal = false; diff --git a/Source/Core/UICommon/DiscordPresence.cpp b/Source/Core/UICommon/DiscordPresence.cpp index 6f742308c0..5fb7ee676b 100644 --- a/Source/Core/UICommon/DiscordPresence.cpp +++ b/Source/Core/UICommon/DiscordPresence.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include "UICommon/DiscordPresence.h" + #include "Common/Hash.h" #include "Common/StringUtil.h" @@ -9,8 +11,6 @@ #include "Core/Config/UISettings.h" #include "Core/ConfigManager.h" -#include "UICommon/DiscordPresence.h" - #ifdef USE_DISCORD_PRESENCE #include @@ -44,7 +44,7 @@ static void HandleDiscordJoin(const char* join_secret) return; if (Config::Get(Config::NETPLAY_NICKNAME) == Config::NETPLAY_NICKNAME.default_value) - Config::SetBaseOrCurrent(Config::NETPLAY_NICKNAME, username); + Config::SetCurrent(Config::NETPLAY_NICKNAME, username); std::string secret(join_secret); @@ -59,22 +59,23 @@ static void HandleDiscordJoin(const char* join_secret) case SecretType::IPAddress: { - Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, "direct"); + // SetBaseOrCurrent will save the ip address, which isn't what's wanted in this situation + Config::SetCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, "direct"); std::string host = secret.substr(offset, secret.find_last_of(':') - offset); - Config::SetBaseOrCurrent(Config::NETPLAY_HOST_CODE, host); + Config::SetCurrent(Config::NETPLAY_ADDRESS, host); offset += host.length(); if (secret[offset] == ':') - Config::SetBaseOrCurrent(Config::NETPLAY_CONNECT_PORT, std::stoul(secret.substr(offset + 1))); + Config::SetCurrent(Config::NETPLAY_CONNECT_PORT, std::stoul(secret.substr(offset + 1))); } break; case SecretType::RoomID: { - Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, "traversal"); + Config::SetCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, "traversal"); - Config::SetBaseOrCurrent(Config::NETPLAY_HOST_CODE, secret.substr(offset)); + Config::SetCurrent(Config::NETPLAY_HOST_CODE, secret.substr(offset)); } break; } @@ -120,7 +121,7 @@ void InitNetPlayFunctionality(Handler& handler) #endif } -void UpdateDiscordPresence(const int party_size, SecretType type, const std::string& secret, +void UpdateDiscordPresence(int party_size, SecretType type, const std::string& secret, const std::string& current_game) { #ifdef USE_DISCORD_PRESENCE @@ -136,7 +137,7 @@ void UpdateDiscordPresence(const int party_size, SecretType type, const std::str discord_presence.details = title.empty() ? "Not in-game" : title.c_str(); discord_presence.startTimestamp = std::time(nullptr); - if (0 < party_size) + if (party_size > 0) { if (party_size < 4) { diff --git a/Source/Core/UICommon/DiscordPresence.h b/Source/Core/UICommon/DiscordPresence.h index c7e921e804..70ff1e46fb 100644 --- a/Source/Core/UICommon/DiscordPresence.h +++ b/Source/Core/UICommon/DiscordPresence.h @@ -5,13 +5,10 @@ #pragma once #include +#include namespace Discord { -using JoinFunction = std::function; -using JoinRequestFunction = - std::function; - class Handler { public: