Qt: Add Discord Rich Presence support

This commit is contained in:
Connor McLaughlin 2022-10-01 22:28:48 +10:00 committed by refractionpcsx2
parent 85b6842557
commit 1186025c89
15 changed files with 225 additions and 29 deletions

View File

@ -1,14 +1,14 @@
set(SRCS
include/discord_register.h
include/discord_rpc.h
src/backoff.h
src/connection.h
src/discord_rpc.cpp
src/msg_queue.h
src/rpc_connection.cpp
src/rpc_connection.h
src/serialization.cpp
src/serialization.h
include/discord_register.h
include/discord_rpc.h
src/backoff.h
src/connection.h
src/discord_rpc.cpp
src/msg_queue.h
src/rpc_connection.cpp
src/rpc_connection.h
src/serialization.cpp
src/serialization.h
)
add_library(discord-rpc ${SRCS})
@ -20,10 +20,10 @@ set_property(TARGET discord-rpc PROPERTY CXX_STANDARD 17)
set_property(TARGET discord-rpc PROPERTY CXX_STANDARD_REQUIRED ON)
if(WIN32)
target_sources(discord-rpc PRIVATE
src/connection_win.cpp
src/discord_register_win.cpp
)
target_sources(discord-rpc PRIVATE
src/connection_win.cpp
src/discord_register_win.cpp
)
elseif(APPLE)
target_sources(discord-rpc PRIVATE
src/connection_unix.cpp

View File

@ -61,6 +61,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rcheevos", "3rdparty\rcheev
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rainterface", "3rdparty\rainterface\rainterface.vcxproj", "{95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "discord-rpc", "3rdparty\discord-rpc\discord-rpc.vcxproj", "{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug AVX2|x64 = Debug AVX2|x64
@ -407,6 +409,18 @@ Global
{95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Release AVX2|x64.Build.0 = Release|x64
{95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Release|x64.ActiveCfg = Release|x64
{95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Release|x64.Build.0 = Release|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Debug AVX2|x64.ActiveCfg = Debug|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Debug AVX2|x64.Build.0 = Debug|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Debug|x64.ActiveCfg = Debug|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Debug|x64.Build.0 = Debug|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Devel AVX2|x64.ActiveCfg = Devel|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Devel AVX2|x64.Build.0 = Devel|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Devel|x64.ActiveCfg = Devel|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Devel|x64.Build.0 = Devel|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Release AVX2|x64.ActiveCfg = Release|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Release AVX2|x64.Build.0 = Release|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Release|x64.ActiveCfg = Release|x64
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -436,6 +450,7 @@ Global
{7E183337-A7E9-460C-9D3D-568BC9F9BCC1} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
{6D5B5AD9-1525-459B-939F-A5E1082AF6B3} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
{95DD0A0C-D14D-4CFF-A593-820EF26EFCC8} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0BC474EA-3628-45D3-9DBC-E22D0B7E0F77}

View File

@ -39,6 +39,7 @@ endif()
option(USE_VTUNE "Plug VTUNE to profile GS JIT.")
option(USE_ACHIEVEMENTS "Build with RetroAchievements support" ON)
option(USE_DISCORD_PRESENCE "Enable support for Discord Rich Presence" ON)
#-------------------------------------------------------------------------------
# Graphical option

View File

@ -239,6 +239,12 @@ if(QT_BUILD)
if(USE_ACHIEVEMENTS)
add_subdirectory(3rdparty/rcheevos EXCLUDE_FROM_ALL)
endif()
# Discord-RPC library for rich presence.
if(USE_DISCORD_PRESENCE)
add_subdirectory(3rdparty/rapidjson EXCLUDE_FROM_ALL)
add_subdirectory(3rdparty/discord-rpc EXCLUDE_FROM_ALL)
endif()
endif()
if(NOT WIN32 AND QT_BUILD)

View File

@ -97,6 +97,12 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
m_ui.automaticUpdaterGroup->hide();
}
#ifdef ENABLE_DISCORD_PRESENCE
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.discordPresence, "EmuCore", "EnableDiscordPresence", false);
#else
m_ui.discordPresence->setEnabled(false);
#endif
dialog->registerWidgetHelp(
m_ui.confirmShutdown, tr("Confirm Shutdown"), tr("Checked"),
tr("Determines whether a prompt will be displayed to confirm shutting down the virtual machine "
@ -119,6 +125,9 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
dialog->registerWidgetHelp(
m_ui.hideMainWindow, tr("Hide Main Window When Running"), tr("Unchecked"),
tr("Hides the main window (with the game list) when a game is running, requires Render To Separate Window to be enabled."));
dialog->registerWidgetHelp(
m_ui.discordPresence, tr("Enable Discord Presence"), tr("Unchecked"),
tr("Shows the game you are currently playing as part of your profile in Discord."));
// Not yet used, disable the options
m_ui.language->setDisabled(true);

View File

@ -67,6 +67,13 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="discordPresence">
<property name="text">
<string>Enable Discord Presence</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -51,7 +51,7 @@
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;LZMA_API_STATIC;BUILD_DX=1;ENABLE_RAINTEGRATION;ENABLE_ACHIEVEMENTS;ENABLE_OPENGL;ENABLE_VULKAN;DIRECTINPUT_VERSION=0x0800;PCSX2_CORE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;LZMA_API_STATIC;BUILD_DX=1;ENABLE_RAINTEGRATION;ENABLE_ACHIEVEMENTS;ENABLE_DISCORD_PRESENCE;ENABLE_OPENGL;ENABLE_VULKAN;DIRECTINPUT_VERSION=0x0800;PCSX2_CORE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Debug))">PCSX2_DEBUG;PCSX2_DEVBUILD;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Devel))">PCSX2_DEVEL;PCSX2_DEVBUILD;NDEBUG;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Release))">NDEBUG;_SECURE_SCL_=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>

View File

@ -1106,6 +1106,14 @@ if(PCSX2_CORE)
rcheevos
)
endif()
if(USE_DISCORD_PRESENCE)
target_compile_definitions(PCSX2_FLAGS INTERFACE
ENABLE_DISCORD_PRESENCE
)
target_link_libraries(PCSX2_FLAGS INTERFACE
discord-rpc
)
endif()
endif()
# gui sources

View File

@ -990,6 +990,7 @@ struct Pcsx2Config
#ifdef PCSX2_CORE
EnableGameFixes : 1, // enables automatic game fixes
SaveStateOnShutdown : 1, // default value for saving state on shutdown
EnableDiscordPresence : 1, // enables discord rich presence integration
#endif
// when enabled uses BOOT2 injection, skipping sony bios splashes
UseBOOT2Injection : 1,

View File

@ -16,6 +16,7 @@
#include "PrecompiledHeader.h"
#include "Frontend/Achievements.h"
#include "Frontend/CommonHost.h"
#include "Frontend/FullscreenUI.h"
#include "Frontend/ImGuiFullscreen.h"
@ -394,6 +395,11 @@ void Achievements::ClearGameInfo(bool clear_achievements, bool clear_leaderboard
s_rich_presence_string = {};
s_has_rich_presence = false;
s_game_id = 0;
#ifdef ENABLE_DISCORD_PRESENCE
if (EmuConfig.EnableDiscordPresence)
CommonHost::UpdateDiscordPresence(s_rich_presence_string);
#endif
}
if (had_game)
@ -1499,22 +1505,30 @@ void Achievements::UpdateRichPresence()
char buffer[512];
const int res = rc_runtime_get_richpresence(&s_rcheevos_runtime, buffer, sizeof(buffer), PeekMemory, nullptr, nullptr);
if (res <= 0)
{
const bool had_rich_presence = !s_rich_presence_string.empty();
s_rich_presence_string.clear();
if (had_rich_presence)
Host::OnAchievementsRefreshed();
return;
}
std::unique_lock lock(s_achievements_mutex);
if (s_rich_presence_string == buffer)
return;
const bool had_rich_presence = !s_rich_presence_string.empty();
if (res <= 0)
{
if (!had_rich_presence)
return;
s_rich_presence_string.clear();
}
else
{
if (s_rich_presence_string == buffer)
return;
s_rich_presence_string.assign(buffer);
}
s_rich_presence_string.assign(buffer);
Host::OnAchievementsRefreshed();
#ifdef ENABLE_DISCORD_PRESENCE
if (EmuConfig.EnableDiscordPresence)
CommonHost::UpdateDiscordPresence(s_rich_presence_string);
#endif
}
void Achievements::SendPingCallback(s32 status_code, const std::string& content_type, Common::HTTPDownloader::Request::Data data)

View File

@ -40,6 +40,10 @@
#include "Frontend/Achievements.h"
#endif
#ifdef ENABLE_DISCORD_PRESENCE
#include "discord_rpc.h"
#endif
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#include <KnownFolders.h>
@ -55,8 +59,19 @@ namespace CommonHost
static bool ShouldUsePortableMode();
static void SetDataDirectory();
static void SetCommonDefaultSettings(SettingsInterface& si);
#ifdef ENABLE_DISCORD_PRESENCE
static void InitializeDiscordPresence();
static void ShutdownDiscordPresence();
static void PollDiscordPresence();
static std::string GetRichPresenceString();
#endif
} // namespace CommonHost
#ifdef ENABLE_DISCORD_PRESENCE
static bool s_discord_presence_active = false;
#endif
bool CommonHost::InitializeCriticalFolders()
{
SetAppRoot();
@ -237,10 +252,19 @@ void CommonHost::CPUThreadInitialize()
if (EmuConfig.Achievements.Enabled)
Achievements::Initialize();
#endif
#ifdef ENABLE_DISCORD_PRESENCE
if (EmuConfig.EnableDiscordPresence)
InitializeDiscordPresence();
#endif
}
void CommonHost::CPUThreadShutdown()
{
#ifdef ENABLE_DISCORD_PRESENCE
ShutdownDiscordPresence();
#endif
#ifdef ENABLE_ACHIEVEMENTS
Achievements::Shutdown();
#endif
@ -269,6 +293,16 @@ void CommonHost::CheckForSettingsChanges(const Pcsx2Config& old_config)
#endif
FullscreenUI::CheckForConfigChanges(old_config);
#ifdef ENABLE_DISCORD_PRESENCE
if (EmuConfig.EnableDiscordPresence != old_config.EnableDiscordPresence)
{
if (EmuConfig.EnableDiscordPresence)
InitializeDiscordPresence();
else
ShutdownDiscordPresence();
}
#endif
}
void CommonHost::OnVMStarting()
@ -317,6 +351,10 @@ void CommonHost::OnGameChanged(const std::string& disc_path, const std::string&
#ifdef ENABLE_ACHIEVEMENTS
Achievements::GameChanged(game_crc);
#endif
#ifdef ENABLE_DISCORD_PRESENCE
UpdateDiscordPresence(GetRichPresenceString());
#endif
}
void CommonHost::CPUThreadVSync()
@ -326,6 +364,10 @@ void CommonHost::CPUThreadVSync()
Achievements::VSyncUpdate();
#endif
#ifdef ENABLE_DISCORD_PRESENCE
PollDiscordPresence();
#endif
InputManager::PollSources();
}
@ -352,3 +394,86 @@ bool Host::GetSerialAndCRCForFilename(const char* filename, std::string* serial,
return false;
}
#ifdef ENABLE_DISCORD_PRESENCE
void CommonHost::InitializeDiscordPresence()
{
if (s_discord_presence_active)
return;
DiscordEventHandlers handlers = {};
Discord_Initialize("1025789002055430154", &handlers, 0, nullptr);
s_discord_presence_active = true;
UpdateDiscordPresence(GetRichPresenceString());
}
void CommonHost::ShutdownDiscordPresence()
{
if (!s_discord_presence_active)
return;
Discord_ClearPresence();
Discord_RunCallbacks();
Discord_Shutdown();
s_discord_presence_active = false;
}
void CommonHost::UpdateDiscordPresence(const std::string& rich_presence)
{
if (!s_discord_presence_active)
return;
// https://discord.com/developers/docs/rich-presence/how-to#updating-presence-update-presence-payload-fields
DiscordRichPresence rp = {};
rp.largeImageKey = "4k-pcsx2";
rp.largeImageText = "PCSX2 Emulator";
rp.startTimestamp = std::time(nullptr);
std::string details_string;
if (VMManager::HasValidVM())
details_string = VMManager::GetGameName();
else
details_string = "No Game Running";
rp.details = details_string.c_str();
// Trim to 128 bytes as per Discord-RPC requirements
std::string state_string;
if (rich_presence.length() >= 128)
{
// 124 characters + 3 dots + null terminator
state_string = fmt::format("{}...", std::string_view(rich_presence).substr(0, 124));
}
else
{
state_string = rich_presence;
}
rp.state = state_string.c_str();
Discord_UpdatePresence(&rp);
Discord_RunCallbacks();
}
void CommonHost::PollDiscordPresence()
{
if (!s_discord_presence_active)
return;
Discord_RunCallbacks();
}
std::string CommonHost::GetRichPresenceString()
{
std::string rich_presence_string;
#ifdef ENABLE_ACHIEVEMENTS
if (Achievements::IsActive() && EmuConfig.Achievements.RichPresence)
{
auto lock = Achievements::GetLock();
rich_presence_string = Achievements::GetRichPresenceString();
}
#endif
return rich_presence_string;
}
#endif

View File

@ -76,6 +76,11 @@ namespace CommonHost
/// Provided by the host; called once per frame at guest vsync.
void CPUThreadVSync();
#ifdef ENABLE_DISCORD_PRESENCE
/// Called when the rich presence string, provided by RetroAchievements, changes.
void UpdateDiscordPresence(const std::string& rich_presence);
#endif
namespace Internal
{
/// Resets any state for hotkey-related VMs, called on VM startup.

View File

@ -1100,6 +1100,7 @@ void Pcsx2Config::LoadSave(SettingsWrapper& wrap)
#ifdef PCSX2_CORE
SettingsWrapBitBool(EnableGameFixes);
SettingsWrapBitBool(SaveStateOnShutdown);
SettingsWrapBitBool(EnableDiscordPresence);
#endif
SettingsWrapBitBool(ConsoleToStdio);
SettingsWrapBitBool(HostFs);

View File

@ -119,7 +119,7 @@ static Threading::ThreadHandle s_vm_thread_handle;
static std::deque<std::thread> s_save_state_threads;
static std::mutex s_save_state_threads_mutex;
static std::mutex s_info_mutex;
static std::recursive_mutex s_info_mutex;
static std::string s_disc_path;
static u32 s_game_crc;
static u32 s_patches_crc;

View File

@ -51,13 +51,14 @@
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\cpuinfo\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rcheevos\rcheevos\include;$(SolutionDir)3rdparty\rainterface</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\discord-rpc\include</AdditionalIncludeDirectories>
<ExceptionHandling>Async</ExceptionHandling>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
<ForcedIncludeFiles>PrecompiledHeader.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
<AdditionalOptions>/Zc:externConstexpr %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;ZIP_STATIC;LZMA_API_STATIC;BUILD_DX=1;ENABLE_RAINTEGRATION;ENABLE_ACHIEVEMENTS;ENABLE_OPENGL;ENABLE_VULKAN;SPU2X_CUBEB;SDL_BUILD;PCSX2_CORE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;ZIP_STATIC;LZMA_API_STATIC;BUILD_DX=1;ENABLE_DISCORD_PRESENCE;ENABLE_RAINTEGRATION;ENABLE_ACHIEVEMENTS;ENABLE_OPENGL;ENABLE_VULKAN;SPU2X_CUBEB;SDL_BUILD;PCSX2_CORE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Debug))">PCSX2_DEBUG;PCSX2_DEVBUILD;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Devel))">PCSX2_DEVEL;PCSX2_DEVBUILD;NDEBUG;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(Configuration.Contains(Release))">NDEBUG;_SECURE_SCL_=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -805,6 +806,9 @@
<ProjectReference Include="$(SolutionDir)3rdparty\imgui\imgui.vcxproj">
<Project>{88fb34ec-845e-4f21-a552-f1573b9ed167}</Project>
</ProjectReference>
<ProjectReference Include="..\3rdparty\discord-rpc\discord-rpc.vcxproj">
<Project>{e960dfdf-1bd3-4c29-b251-d1a0919c9b09}</Project>
</ProjectReference>
<ProjectReference Include="..\3rdparty\libzip\libzip.vcxproj">
<Project>{20b2e9fe-f020-42a0-b324-956f5b06ea68}</Project>
</ProjectReference>