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
This commit is contained in:
parent
b7c241ea4c
commit
c2aedb7649
|
@ -1330,6 +1330,7 @@ void NetPlayClient::OnTraversalStateChanged()
|
||||||
Disconnect();
|
Disconnect();
|
||||||
m_dialog->OnTraversalError(m_traversal_client->GetFailureReason());
|
m_dialog->OnTraversalError(m_traversal_client->GetFailureReason());
|
||||||
}
|
}
|
||||||
|
m_dialog->OnTraversalStateChanged(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// called from ---NETPLAY--- thread
|
// called from ---NETPLAY--- thread
|
||||||
|
|
|
@ -47,6 +47,7 @@ public:
|
||||||
virtual void OnConnectionLost() = 0;
|
virtual void OnConnectionLost() = 0;
|
||||||
virtual void OnConnectionError(const std::string& message) = 0;
|
virtual void OnConnectionError(const std::string& message) = 0;
|
||||||
virtual void OnTraversalError(TraversalClient::FailureReason error) = 0;
|
virtual void OnTraversalError(TraversalClient::FailureReason error) = 0;
|
||||||
|
virtual void OnTraversalStateChanged(TraversalClient::State state) = 0;
|
||||||
virtual void OnSaveDataSyncFailure() = 0;
|
virtual void OnSaveDataSyncFailure() = 0;
|
||||||
|
|
||||||
virtual bool IsRecording() = 0;
|
virtual bool IsRecording() = 0;
|
||||||
|
|
|
@ -809,8 +809,15 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
|
||||||
|
|
||||||
void NetPlayServer::OnTraversalStateChanged()
|
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->OnTraversalError(m_traversal_client->GetFailureReason());
|
||||||
|
|
||||||
|
m_dialog->OnTraversalStateChanged(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// called from ---GUI--- thread
|
// called from ---GUI--- thread
|
||||||
|
|
|
@ -9,6 +9,7 @@ add_executable(dolphin-emu
|
||||||
AboutDialog.cpp
|
AboutDialog.cpp
|
||||||
CheatsManager.cpp
|
CheatsManager.cpp
|
||||||
DiscordHandler.cpp
|
DiscordHandler.cpp
|
||||||
|
DiscordJoinRequestDialog.cpp
|
||||||
FIFO/FIFOPlayerWindow.cpp
|
FIFO/FIFOPlayerWindow.cpp
|
||||||
FIFO/FIFOAnalyzer.cpp
|
FIFO/FIFOAnalyzer.cpp
|
||||||
HotkeyScheduler.cpp
|
HotkeyScheduler.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 <iterator>
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
#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
|
|
@ -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 <list>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#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<DiscordJoinRequestDialog> m_request_dialogs;
|
||||||
|
};
|
|
@ -0,0 +1,90 @@
|
||||||
|
// Copyright 2018 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
|
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
#include <discord-rpc/include/discord_rpc.h>
|
||||||
|
|
||||||
|
#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<uint>(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
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2018 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
|
@ -108,6 +108,7 @@
|
||||||
<QtMoc Include="Config\PropertiesDialog.h" />
|
<QtMoc Include="Config\PropertiesDialog.h" />
|
||||||
<QtMoc Include="Config\SettingsWindow.h" />
|
<QtMoc Include="Config\SettingsWindow.h" />
|
||||||
<QtMoc Include="DiscordHandler.h" />
|
<QtMoc Include="DiscordHandler.h" />
|
||||||
|
<QtMoc Include="DiscordJoinRequestDialog.h" />
|
||||||
<QtMoc Include="FIFO\FIFOAnalyzer.h" />
|
<QtMoc Include="FIFO\FIFOAnalyzer.h" />
|
||||||
<QtMoc Include="FIFO\FIFOPlayerWindow.h" />
|
<QtMoc Include="FIFO\FIFOPlayerWindow.h" />
|
||||||
<QtMoc Include="TAS\GCTASInputWindow.h" />
|
<QtMoc Include="TAS\GCTASInputWindow.h" />
|
||||||
|
@ -177,6 +178,7 @@
|
||||||
<ClCompile Include="$(QtMocOutPrefix)CodeWidget.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)CodeWidget.cpp" />
|
||||||
<ClCompile Include="$(QtMocOutPrefix)ControllersWindow.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)ControllersWindow.cpp" />
|
||||||
<ClCompile Include="$(QtMocOutPrefix)DiscordHandler.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)DiscordHandler.cpp" />
|
||||||
|
<ClCompile Include="$(QtMocOutPrefix)DiscordJoinRequestDialog.cpp" />
|
||||||
<ClCompile Include="$(QtMocOutPrefix)DoubleClickEventFilter.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)DoubleClickEventFilter.cpp" />
|
||||||
<ClCompile Include="$(QtMocOutPrefix)ElidedButton.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)ElidedButton.cpp" />
|
||||||
<ClCompile Include="$(QtMocOutPrefix)FlowLayout.cpp" />
|
<ClCompile Include="$(QtMocOutPrefix)FlowLayout.cpp" />
|
||||||
|
@ -323,6 +325,7 @@
|
||||||
<ClCompile Include="Debugger\MemoryWidget.cpp" />
|
<ClCompile Include="Debugger\MemoryWidget.cpp" />
|
||||||
<ClCompile Include="Debugger\MemoryViewWidget.cpp" />
|
<ClCompile Include="Debugger\MemoryViewWidget.cpp" />
|
||||||
<ClCompile Include="DiscordHandler.cpp" />
|
<ClCompile Include="DiscordHandler.cpp" />
|
||||||
|
<ClCompile Include="DiscordJoinRequestDialog.cpp" />
|
||||||
<ClCompile Include="FIFO\FIFOAnalyzer.cpp" />
|
<ClCompile Include="FIFO\FIFOAnalyzer.cpp" />
|
||||||
<ClCompile Include="FIFO\FIFOPlayerWindow.cpp" />
|
<ClCompile Include="FIFO\FIFOPlayerWindow.cpp" />
|
||||||
<ClCompile Include="QtUtils\WinIconHelper.cpp" />
|
<ClCompile Include="QtUtils\WinIconHelper.cpp" />
|
||||||
|
|
|
@ -1044,12 +1044,11 @@ void MainWindow::BootWiiSystemMenu()
|
||||||
void MainWindow::NetPlayInit()
|
void MainWindow::NetPlayInit()
|
||||||
{
|
{
|
||||||
m_netplay_setup_dialog = new NetPlaySetupDialog(this);
|
m_netplay_setup_dialog = new NetPlaySetupDialog(this);
|
||||||
<<<<<<< HEAD:Source/Core/DolphinQt/MainWindow.cpp
|
|
||||||
m_netplay_dialog = new NetPlayDialog;
|
m_netplay_dialog = new NetPlayDialog;
|
||||||
=======
|
|
||||||
m_netplay_dialog = new NetPlayDialog(this);
|
m_netplay_dialog = new NetPlayDialog(this);
|
||||||
m_netplay_discord = new DiscordHandler();
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
>>>>>>> Add Discord Join Net Play functionally:Source/Core/DolphinQt2/MainWindow.cpp
|
m_netplay_discord = new DiscordHandler(this);
|
||||||
|
#endif
|
||||||
|
|
||||||
connect(m_netplay_dialog, &NetPlayDialog::Boot, this,
|
connect(m_netplay_dialog, &NetPlayDialog::Boot, this,
|
||||||
[this](const QString& path) { StartGame(path); });
|
[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_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit);
|
||||||
connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin);
|
connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin);
|
||||||
connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Host, this, &MainWindow::NetPlayHost);
|
connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Host, this, &MainWindow::NetPlayHost);
|
||||||
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
connect(m_netplay_discord, &DiscordHandler::Join, this, &MainWindow::NetPlayJoin);
|
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();
|
m_netplay_discord->Start();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::NetPlayJoin()
|
bool MainWindow::NetPlayJoin()
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
|
|
||||||
#include "Core/Config/GraphicsSettings.h"
|
#include "Core/Config/GraphicsSettings.h"
|
||||||
#include "Core/Config/MainSettings.h"
|
#include "Core/Config/MainSettings.h"
|
||||||
|
#include "Core/Config/NetplaySettings.h"
|
||||||
#include "Core/Config/SYSCONFSettings.h"
|
#include "Core/Config/SYSCONFSettings.h"
|
||||||
#include "Core/ConfigLoaders/GameConfigLoader.h"
|
#include "Core/ConfigLoaders/GameConfigLoader.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
|
@ -461,6 +462,55 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal)
|
||||||
UpdateGUI();
|
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()
|
void NetPlayDialog::UpdateGUI()
|
||||||
{
|
{
|
||||||
auto client = Settings::Instance().GetNetPlayClient();
|
auto client = Settings::Instance().GetNetPlayClient();
|
||||||
|
@ -569,39 +619,10 @@ void NetPlayDialog::UpdateGUI()
|
||||||
m_hostcode_action_button->setEnabled(true);
|
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)
|
UpdateDiscordPresence();
|
||||||
{
|
m_old_player_count = m_player_count;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -670,6 +691,7 @@ void NetPlayDialog::OnMsgChangeGame(const std::string& title)
|
||||||
QueueOnObject(this, [this, qtitle, title] {
|
QueueOnObject(this, [this, qtitle, title] {
|
||||||
m_game_button->setText(qtitle);
|
m_game_button->setText(qtitle);
|
||||||
m_current_game = title;
|
m_current_game = title;
|
||||||
|
UpdateDiscordPresence();
|
||||||
});
|
});
|
||||||
DisplayMessage(tr("Game changed to \"%1\"").arg(qtitle), "magenta");
|
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()
|
void NetPlayDialog::OnSaveDataSyncFailure()
|
||||||
{
|
{
|
||||||
QueueOnObject(this, [this] { SetOptionsEnabled(true); });
|
QueueOnObject(this, [this] { SetOptionsEnabled(true); });
|
||||||
|
|
|
@ -51,6 +51,7 @@ public:
|
||||||
void OnConnectionLost() override;
|
void OnConnectionLost() override;
|
||||||
void OnConnectionError(const std::string& message) override;
|
void OnConnectionError(const std::string& message) override;
|
||||||
void OnTraversalError(TraversalClient::FailureReason error) override;
|
void OnTraversalError(TraversalClient::FailureReason error) override;
|
||||||
|
void OnTraversalStateChanged(TraversalClient::State state) override;
|
||||||
void OnSaveDataSyncFailure() override;
|
void OnSaveDataSyncFailure() override;
|
||||||
|
|
||||||
bool IsRecording() override;
|
bool IsRecording() override;
|
||||||
|
@ -73,6 +74,7 @@ private:
|
||||||
void OnStart();
|
void OnStart();
|
||||||
void DisplayMessage(const QString& msg, const std::string& color,
|
void DisplayMessage(const QString& msg, const std::string& color,
|
||||||
int duration = OSD::Duration::NORMAL);
|
int duration = OSD::Duration::NORMAL);
|
||||||
|
void UpdateDiscordPresence();
|
||||||
void UpdateGUI();
|
void UpdateGUI();
|
||||||
void GameStatusChanged(bool running);
|
void GameStatusChanged(bool running);
|
||||||
void SetOptionsEnabled(bool enabled);
|
void SetOptionsEnabled(bool enabled);
|
||||||
|
@ -113,6 +115,7 @@ private:
|
||||||
MD5Dialog* m_md5_dialog;
|
MD5Dialog* m_md5_dialog;
|
||||||
PadMappingDialog* m_pad_mapping;
|
PadMappingDialog* m_pad_mapping;
|
||||||
std::string m_current_game;
|
std::string m_current_game;
|
||||||
|
std::string m_exernal_ip_address;
|
||||||
std::string m_nickname;
|
std::string m_nickname;
|
||||||
GameListModel* m_game_list_model = nullptr;
|
GameListModel* m_game_list_model = nullptr;
|
||||||
bool m_use_traversal = false;
|
bool m_use_traversal = false;
|
||||||
|
@ -120,4 +123,5 @@ private:
|
||||||
bool m_got_stop_request = true;
|
bool m_got_stop_request = true;
|
||||||
int m_buffer_size = 0;
|
int m_buffer_size = 0;
|
||||||
int m_player_count = 0;
|
int m_player_count = 0;
|
||||||
|
int m_old_player_count = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
// Copyright 2018 Dolphin Emulator Project
|
|
||||||
// Licensed under GPLv2+
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#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;
|
|
||||||
};
|
|
|
@ -3,6 +3,7 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "Common/Hash.h"
|
#include "Common/Hash.h"
|
||||||
|
#include "Common/StringUtil.h"
|
||||||
|
|
||||||
#include "Core/Config/NetplaySettings.h"
|
#include "Core/Config/NetplaySettings.h"
|
||||||
#include "Core/Config/UISettings.h"
|
#include "Core/Config/UISettings.h"
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
#ifdef USE_DISCORD_PRESENCE
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
static JoinFunction join_function = nullptr;
|
static Handler* event_handler = nullptr;
|
||||||
static const char* username = "";
|
static const char* username = "";
|
||||||
|
|
||||||
static void HandleDiscordReady(const DiscordUser* user)
|
static void HandleDiscordReady(const DiscordUser* user)
|
||||||
|
@ -28,9 +29,18 @@ static void HandleDiscordReady(const DiscordUser* user)
|
||||||
username = user->username;
|
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)
|
static void HandleDiscordJoin(const char* join_secret)
|
||||||
{
|
{
|
||||||
if (join_function == nullptr)
|
if (event_handler == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Config::Get(Config::NETPLAY_NICKNAME) == Config::NETPLAY_NICKNAME.default_value)
|
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);
|
std::string secret(join_secret);
|
||||||
|
|
||||||
size_t offset = 0;
|
std::string type = secret.substr(0, secret.find('\n'));
|
||||||
std::string type = secret.substr(offset, secret.find('\n'));
|
size_t offset = type.length() + 1;
|
||||||
offset += type.length() + 1;
|
|
||||||
|
|
||||||
switch (static_cast<SecretType>(std::stol(type)))
|
switch (static_cast<SecretType>(std::stol(type)))
|
||||||
{
|
{
|
||||||
|
@ -70,10 +79,12 @@ static void HandleDiscordJoin(const char* join_secret)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
join_function();
|
event_handler->DiscordJoin();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Discord::Handler::~Handler() = default;
|
||||||
|
|
||||||
void Init()
|
void Init()
|
||||||
{
|
{
|
||||||
#ifdef USE_DISCORD_PRESENCE
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
|
@ -83,6 +94,7 @@ void Init()
|
||||||
DiscordEventHandlers handlers = {};
|
DiscordEventHandlers handlers = {};
|
||||||
|
|
||||||
handlers.ready = HandleDiscordReady;
|
handlers.ready = HandleDiscordReady;
|
||||||
|
handlers.joinRequest = HandleDiscordJoinRequest;
|
||||||
handlers.joinGame = HandleDiscordJoin;
|
handlers.joinGame = HandleDiscordJoin;
|
||||||
// The number is the client ID for Dolphin, it's used for images and the appication name
|
// The number is the client ID for Dolphin, it's used for images and the appication name
|
||||||
Discord_Initialize("455712169795780630", &handlers, 1, nullptr);
|
Discord_Initialize("455712169795780630", &handlers, 1, nullptr);
|
||||||
|
@ -101,20 +113,22 @@ void CallPendingCallbacks()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitNetPlayFunctionality(const JoinFunction& join)
|
void InitNetPlayFunctionality(Handler& handler)
|
||||||
{
|
{
|
||||||
#ifdef USE_DISCORD_PRESENCE
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
join_function = std::move(join);
|
event_handler = &handler;
|
||||||
#endif
|
#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
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE))
|
if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const std::string& title = SConfig::GetInstance().GetTitleDescription();
|
const std::string& title =
|
||||||
|
current_game.empty() ? SConfig::GetInstance().GetTitleDescription() : current_game;
|
||||||
|
|
||||||
DiscordRichPresence discord_presence = {};
|
DiscordRichPresence discord_presence = {};
|
||||||
discord_presence.largeImageKey = "dolphin_logo";
|
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;
|
std::string secret_final;
|
||||||
if (type != SecretType::Empty)
|
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.
|
// values over to Discord_UpdatePresence.
|
||||||
|
|
||||||
const size_t secret_length = secret.length();
|
const size_t secret_length = secret.length();
|
||||||
party_ID = std::to_string(
|
party_id = std::to_string(
|
||||||
Common::HashAdler32(reinterpret_cast<const u8*>(secret.c_str()), secret_length));
|
Common::HashAdler32(reinterpret_cast<const u8*>(secret.c_str()), secret_length));
|
||||||
|
|
||||||
const std::string secret_type = std::to_string(static_cast<int>(type));
|
const std::string secret_type = std::to_string(static_cast<int>(type));
|
||||||
|
@ -156,13 +170,24 @@ void UpdateDiscordPresence(const int party_size, SecretType type, const std::str
|
||||||
secret_final += '\n';
|
secret_final += '\n';
|
||||||
secret_final += secret;
|
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_presence.joinSecret = secret_final.c_str();
|
||||||
|
|
||||||
Discord_UpdatePresence(&discord_presence);
|
Discord_UpdatePresence(&discord_presence);
|
||||||
#endif
|
#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()
|
void Shutdown()
|
||||||
{
|
{
|
||||||
#ifdef USE_DISCORD_PRESENCE
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
|
|
|
@ -9,8 +9,19 @@
|
||||||
namespace Discord
|
namespace Discord
|
||||||
{
|
{
|
||||||
using JoinFunction = std::function<void()>;
|
using JoinFunction = std::function<void()>;
|
||||||
|
using JoinRequestFunction =
|
||||||
|
std::function<void(const char* id, const std::string& discord_tag, const char* avatar)>;
|
||||||
|
|
||||||
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,
|
Empty,
|
||||||
IPAddress,
|
IPAddress,
|
||||||
|
@ -18,10 +29,11 @@ enum class SecretType : char
|
||||||
};
|
};
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
void InitNetPlayFunctionality(const JoinFunction& join);
|
void InitNetPlayFunctionality(Handler& handler);
|
||||||
void CallPendingCallbacks();
|
void CallPendingCallbacks();
|
||||||
void UpdateDiscordPresence(int party_size = 0, SecretType type = SecretType::Empty,
|
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 Shutdown();
|
||||||
void SetDiscordPresenceEnabled(bool enabled);
|
void SetDiscordPresenceEnabled(bool enabled);
|
||||||
} // namespace Discord
|
} // namespace Discord
|
||||||
|
|
Loading…
Reference in New Issue