Add Discord Rich Presence Support (#3883)
* Initial Discord RPC support Build with Discord Presence ON Fix RPC detection Fix Time elapsed on pause; will now continue to count. * Fix CI builds with compile flag Addressed reviews Fix silly mistakes Fix 'Not in-game' display class instead of namespace Fix Revamped remove redundant code Using Pimpl pattern * Implement Null class * Fix config updation * Addressed All Reviews * externals/discord-rpc : Updated to latest commit
This commit is contained in:
parent
96c025e4c2
commit
6cb9a45154
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -34,3 +34,6 @@
|
||||
[submodule "cubeb"]
|
||||
path = externals/cubeb
|
||||
url = https://github.com/kinetiknz/cubeb.git
|
||||
[submodule "discord-rpc"]
|
||||
path = externals/discord-rpc
|
||||
url = https://github.com/discordapp/discord-rpc.git
|
||||
|
@ -13,3 +13,4 @@ TRAVIS_TAG
|
||||
|
||||
# citra specific flags
|
||||
ENABLE_COMPATIBILITY_REPORTING
|
||||
USE_DISCORD_PRESENCE
|
||||
|
@ -3,7 +3,7 @@
|
||||
cd /citra
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
|
||||
make -j4
|
||||
|
||||
ctest -VV -C Release
|
||||
|
@ -3,7 +3,7 @@
|
||||
cd /citra
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
|
||||
make -j4
|
||||
|
||||
ctest -VV -C Release
|
||||
|
@ -7,7 +7,7 @@ export Qt5_DIR=$(brew --prefix)/opt/qt5
|
||||
export PATH="/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON
|
||||
cmake .. -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
|
||||
make -j4
|
||||
|
||||
ctest -VV -C Release
|
||||
|
@ -20,6 +20,8 @@ option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
|
||||
message(STATUS "Copying pre-commit hook")
|
||||
file(COPY hooks/pre-commit
|
||||
|
@ -44,9 +44,9 @@ before_build:
|
||||
$COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING}
|
||||
if ($env:BUILD_TYPE -eq 'msvc') {
|
||||
# redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
|
||||
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1 && exit 0'
|
||||
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0'
|
||||
} else {
|
||||
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1"
|
||||
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON .. 2>&1"
|
||||
}
|
||||
- cd ..
|
||||
|
||||
|
18
externals/CMakeLists.txt
vendored
18
externals/CMakeLists.txt
vendored
@ -62,6 +62,18 @@ endif()
|
||||
add_subdirectory(enet)
|
||||
target_include_directories(enet INTERFACE ./enet/include)
|
||||
|
||||
# Cubeb
|
||||
if (ENABLE_CUBEB)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# DiscordRPC
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
add_subdirectory(discord-rpc)
|
||||
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
# LibreSSL
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
@ -80,9 +92,3 @@ if (ENABLE_WEB_SERVICE)
|
||||
add_library(json-headers INTERFACE)
|
||||
target_include_directories(json-headers INTERFACE ./json)
|
||||
endif()
|
||||
|
||||
# Cubeb
|
||||
if(ENABLE_CUBEB)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
1
externals/discord-rpc
vendored
Submodule
1
externals/discord-rpc
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 3d3ae7129d17643bc706da0a2eea85aafd10ab3a
|
@ -68,6 +68,7 @@ add_executable(citra-qt
|
||||
debugger/registers.h
|
||||
debugger/wait_tree.cpp
|
||||
debugger/wait_tree.h
|
||||
discord.h
|
||||
game_list.cpp
|
||||
game_list.h
|
||||
game_list_p.h
|
||||
@ -201,6 +202,15 @@ target_link_libraries(citra-qt PRIVATE audio_core common core input_common netwo
|
||||
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::OpenGL Qt5::Widgets Qt5::Multimedia)
|
||||
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
target_sources(citra-qt PUBLIC
|
||||
discord_impl.cpp
|
||||
discord_impl.h
|
||||
)
|
||||
target_link_libraries(citra-qt PRIVATE discord-rpc)
|
||||
target_compile_definitions(citra-qt PRIVATE -DUSE_DISCORD_PRESENCE)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
install(TARGETS citra-qt RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
|
||||
endif()
|
||||
|
@ -207,6 +207,8 @@ void Config::ReadValues() {
|
||||
|
||||
qt_config->beginGroup("UI");
|
||||
UISettings::values.theme = ReadSetting("theme", UISettings::themes[0].second).toString();
|
||||
UISettings::values.enable_discord_presence =
|
||||
ReadSetting("enable_discord_presence", true).toBool();
|
||||
|
||||
qt_config->beginGroup("Updater");
|
||||
UISettings::values.check_for_update_on_start =
|
||||
@ -440,6 +442,7 @@ void Config::SaveValues() {
|
||||
|
||||
qt_config->beginGroup("UI");
|
||||
WriteSetting("theme", UISettings::values.theme, UISettings::themes[0].second);
|
||||
WriteSetting("enable_discord_presence", UISettings::values.enable_discord_presence, true);
|
||||
|
||||
qt_config->beginGroup("Updater");
|
||||
WriteSetting("check_for_update_on_start", UISettings::values.check_for_update_on_start, true);
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <QIcon>
|
||||
#include <QMessageBox>
|
||||
#include "citra_qt/configuration/configure_web.h"
|
||||
#include "citra_qt/ui_settings.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "ui_configure_web.h"
|
||||
@ -17,6 +18,9 @@ ConfigureWeb::ConfigureWeb(QWidget* parent)
|
||||
connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
|
||||
connect(this, &ConfigureWeb::LoginVerified, this, &ConfigureWeb::OnLoginVerified);
|
||||
|
||||
#ifndef USE_DISCORD_PRESENCE
|
||||
ui->discord_group->setVisible(false);
|
||||
#endif
|
||||
this->setConfiguration();
|
||||
}
|
||||
|
||||
@ -49,10 +53,13 @@ void ConfigureWeb::setConfiguration() {
|
||||
ui->label_telemetry_id->setText(
|
||||
tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
|
||||
user_verified = true;
|
||||
|
||||
ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence);
|
||||
}
|
||||
|
||||
void ConfigureWeb::applyConfiguration() {
|
||||
Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
|
||||
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
|
||||
if (user_verified) {
|
||||
Settings::values.citra_username = ui->edit_username->text().toStdString();
|
||||
Settings::values.citra_token = ui->edit_token->text().toStdString();
|
||||
|
@ -170,6 +170,22 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="discord_group">
|
||||
<property name="title">
|
||||
<string>Discord Presence</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_21">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_discordrpc">
|
||||
<property name="text">
|
||||
<string>Show Current Game in your Discord Status</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
25
src/citra_qt/discord.h
Normal file
25
src/citra_qt/discord.h
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace DiscordRPC {
|
||||
|
||||
class DiscordInterface {
|
||||
public:
|
||||
virtual ~DiscordInterface() = default;
|
||||
|
||||
virtual void Pause() = 0;
|
||||
virtual void Update() = 0;
|
||||
};
|
||||
|
||||
class NullImpl : public DiscordInterface {
|
||||
public:
|
||||
~NullImpl() = default;
|
||||
|
||||
void Pause() override {}
|
||||
void Update() override {}
|
||||
};
|
||||
|
||||
} // namespace DiscordRPC
|
51
src/citra_qt/discord_impl.cpp
Normal file
51
src/citra_qt/discord_impl.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <discord_rpc.h>
|
||||
#include "citra_qt/discord_impl.h"
|
||||
#include "citra_qt/ui_settings.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
|
||||
namespace DiscordRPC {
|
||||
|
||||
DiscordImpl::DiscordImpl() {
|
||||
DiscordEventHandlers handlers{};
|
||||
|
||||
// The number is the client ID for Citra, it's used for images and the
|
||||
// application name
|
||||
Discord_Initialize("461729900748079114", &handlers, 1, nullptr);
|
||||
}
|
||||
|
||||
DiscordImpl::~DiscordImpl() {
|
||||
Discord_ClearPresence();
|
||||
Discord_Shutdown();
|
||||
}
|
||||
|
||||
void DiscordImpl::Pause() {
|
||||
Discord_ClearPresence();
|
||||
}
|
||||
|
||||
void DiscordImpl::Update() {
|
||||
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
std::string title;
|
||||
if (Core::System::GetInstance().IsPoweredOn())
|
||||
Core::System::GetInstance().GetAppLoader().ReadTitle(title);
|
||||
DiscordRichPresence presence{};
|
||||
presence.largeImageKey = "citra_logo";
|
||||
presence.largeImageText = "Citra is an emulator for the Nintendo 3DS";
|
||||
if (Core::System::GetInstance().IsPoweredOn()) {
|
||||
presence.state = title.c_str();
|
||||
presence.details = "Currently in game";
|
||||
} else {
|
||||
presence.details = "Not in game";
|
||||
}
|
||||
presence.startTimestamp = start_time;
|
||||
Discord_UpdatePresence(&presence);
|
||||
}
|
||||
} // namespace DiscordRPC
|
20
src/citra_qt/discord_impl.h
Normal file
20
src/citra_qt/discord_impl.h
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "citra_qt/discord.h"
|
||||
|
||||
namespace DiscordRPC {
|
||||
|
||||
class DiscordImpl : public DiscordInterface {
|
||||
public:
|
||||
DiscordImpl();
|
||||
~DiscordImpl();
|
||||
|
||||
void Pause() override;
|
||||
void Update() override;
|
||||
};
|
||||
|
||||
} // namespace DiscordRPC
|
@ -34,6 +34,7 @@
|
||||
#include "citra_qt/debugger/profiler.h"
|
||||
#include "citra_qt/debugger/registers.h"
|
||||
#include "citra_qt/debugger/wait_tree.h"
|
||||
#include "citra_qt/discord.h"
|
||||
#include "citra_qt/game_list.h"
|
||||
#include "citra_qt/hotkeys.h"
|
||||
#include "citra_qt/main.h"
|
||||
@ -58,6 +59,10 @@
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
#include "citra_qt/discord_impl.h"
|
||||
#endif
|
||||
|
||||
#ifdef QT_STATICPLUGIN
|
||||
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
|
||||
#endif
|
||||
@ -120,6 +125,9 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
|
||||
default_theme_paths = QIcon::themeSearchPaths();
|
||||
UpdateUITheme();
|
||||
|
||||
SetDiscordEnabled(UISettings::values.enable_discord_presence);
|
||||
discord_rpc->Update();
|
||||
|
||||
Network::Init();
|
||||
|
||||
InitializeWidgets();
|
||||
@ -748,6 +756,7 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||
}
|
||||
|
||||
void GMainWindow::ShutdownGame() {
|
||||
discord_rpc->Pause();
|
||||
emu_thread->RequestStop();
|
||||
|
||||
// Release emu threads from any breakpoints
|
||||
@ -763,6 +772,8 @@ void GMainWindow::ShutdownGame() {
|
||||
emu_thread->wait();
|
||||
emu_thread = nullptr;
|
||||
|
||||
discord_rpc->Update();
|
||||
|
||||
Camera::QtMultimediaCameraHandler::ReleaseHandlers();
|
||||
|
||||
// The emulation is stopped, so closing the window or not does not matter anymore
|
||||
@ -1049,6 +1060,8 @@ void GMainWindow::OnStartGame() {
|
||||
ui.action_Stop->setEnabled(true);
|
||||
ui.action_Restart->setEnabled(true);
|
||||
ui.action_Report_Compatibility->setEnabled(true);
|
||||
|
||||
discord_rpc->Update();
|
||||
}
|
||||
|
||||
void GMainWindow::OnPauseGame() {
|
||||
@ -1184,11 +1197,14 @@ void GMainWindow::OnConfigure() {
|
||||
connect(&configureDialog, &ConfigureDialog::languageChanged, this,
|
||||
&GMainWindow::OnLanguageChanged);
|
||||
auto old_theme = UISettings::values.theme;
|
||||
const bool old_discord_presence = UISettings::values.enable_discord_presence;
|
||||
auto result = configureDialog.exec();
|
||||
if (result == QDialog::Accepted) {
|
||||
configureDialog.applyConfiguration();
|
||||
if (UISettings::values.theme != old_theme)
|
||||
UpdateUITheme();
|
||||
if (UISettings::values.enable_discord_presence != old_discord_presence)
|
||||
SetDiscordEnabled(UISettings::values.enable_discord_presence);
|
||||
emit UpdateThemedIcons();
|
||||
SyncMenuUISettings();
|
||||
config->Save();
|
||||
@ -1481,6 +1497,19 @@ void GMainWindow::RetranslateStatusBar() {
|
||||
multiplayer_state->retranslateUi();
|
||||
}
|
||||
|
||||
void GMainWindow::SetDiscordEnabled(bool state) {
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
if (state) {
|
||||
discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>();
|
||||
} else {
|
||||
discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
|
||||
}
|
||||
#else
|
||||
discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
|
||||
#endif
|
||||
discord_rpc->Update();
|
||||
}
|
||||
|
||||
#ifdef main
|
||||
#undef main
|
||||
#endif
|
||||
|
@ -38,6 +38,9 @@ class QProgressBar;
|
||||
class RegistersWidget;
|
||||
class Updater;
|
||||
class WaitTreeWidget;
|
||||
namespace DiscordRPC {
|
||||
class DiscordInterface;
|
||||
}
|
||||
|
||||
class GMainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
@ -61,6 +64,7 @@ public:
|
||||
~GMainWindow();
|
||||
|
||||
GameList* game_list;
|
||||
std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
|
||||
|
||||
signals:
|
||||
|
||||
@ -108,6 +112,7 @@ private:
|
||||
void ShowUpdatePrompt();
|
||||
void ShowNoUpdatePrompt();
|
||||
void CheckForUpdates();
|
||||
void SetDiscordEnabled(bool state);
|
||||
|
||||
/**
|
||||
* Stores the filename in the recently loaded files list.
|
||||
|
@ -58,6 +58,9 @@ struct Values {
|
||||
bool update_on_close;
|
||||
bool check_for_update_on_start;
|
||||
|
||||
// Discord RPC
|
||||
bool enable_discord_presence;
|
||||
|
||||
QString roms_path;
|
||||
QString symbols_path;
|
||||
QString game_dir_deprecated;
|
||||
|
Loading…
Reference in New Issue
Block a user