Add Discord Rich Presence integration for Windows

This commit is contained in:
Cameron Gutman 2019-06-29 17:40:30 -07:00
parent 82b6b60b31
commit 0a5051f959
15 changed files with 243 additions and 0 deletions

View file

@ -108,6 +108,10 @@ win32 {
} }
win32:!winrt { win32:!winrt {
CONFIG += soundio CONFIG += soundio
# Discord RPC for rich presence
LIBS += -ldiscord-rpc
DEFINES += HAVE_DISCORD
} }
macx { macx {
LIBS += -lssl -lcrypto -lavcodec.58 -lavutil.56 -lopus -framework SDL2 -framework SDL2_ttf LIBS += -lssl -lcrypto -lavcodec.58 -lavutil.56 -lopus -framework SDL2 -framework SDL2_ttf
@ -128,6 +132,7 @@ SOURCES += \
backend/nvpairingmanager.cpp \ backend/nvpairingmanager.cpp \
backend/computermanager.cpp \ backend/computermanager.cpp \
backend/boxartmanager.cpp \ backend/boxartmanager.cpp \
backend/richpresencemanager.cpp \
cli/commandlineparser.cpp \ cli/commandlineparser.cpp \
cli/quitstream.cpp \ cli/quitstream.cpp \
cli/startstream.cpp \ cli/startstream.cpp \
@ -155,6 +160,7 @@ HEADERS += \
backend/nvpairingmanager.h \ backend/nvpairingmanager.h \
backend/computermanager.h \ backend/computermanager.h \
backend/boxartmanager.h \ backend/boxartmanager.h \
backend/richpresencemanager.h \
cli/commandlineparser.h \ cli/commandlineparser.h \
cli/quitstream.h \ cli/quitstream.h \
cli/startstream.h \ cli/startstream.h \

View file

@ -0,0 +1,63 @@
#include "richpresencemanager.h"
#include <QDebug>
RichPresenceManager::RichPresenceManager(StreamingPreferences& prefs, QString gameName)
: m_DiscordActive(false)
{
#ifdef HAVE_DISCORD
if (prefs.richPresence) {
DiscordEventHandlers handlers = {};
handlers.ready = discordReady;
handlers.disconnected = discordDisconnected;
handlers.errored = discordErrored;
Discord_Initialize("594668102021677159", &handlers, 0, nullptr);
m_DiscordActive = true;
}
if (m_DiscordActive) {
QByteArray stateStr = (QString("Streaming ") + gameName).toUtf8();
DiscordRichPresence discordPresence = {};
discordPresence.state = stateStr.data();
discordPresence.startTimestamp = time(nullptr);
Discord_UpdatePresence(&discordPresence);
}
#endif
}
RichPresenceManager::~RichPresenceManager()
{
#ifdef HAVE_DISCORD
if (m_DiscordActive) {
Discord_ClearPresence();
Discord_Shutdown();
}
#endif
}
void RichPresenceManager::runCallbacks()
{
#ifdef HAVE_DISCORD
if (m_DiscordActive) {
Discord_RunCallbacks();
}
#endif
}
#ifdef HAVE_DISCORD
void RichPresenceManager::discordReady(const DiscordUser* request)
{
qInfo() << "Discord integration ready for user:" << request->username;
}
void RichPresenceManager::discordDisconnected(int errorCode, const char *message)
{
qInfo() << "Discord integration disconnected:" << errorCode << message;
}
void RichPresenceManager::discordErrored(int errorCode, const char *message)
{
qWarning() << "Discord integration error:" << errorCode << message;
}
#endif

View file

@ -0,0 +1,26 @@
#pragma once
#include "settings/streamingpreferences.h"
#ifdef HAVE_DISCORD
#include <discord_rpc.h>
#endif
class RichPresenceManager
{
public:
RichPresenceManager(StreamingPreferences& prefs, QString gameName);
~RichPresenceManager();
void runCallbacks();
private:
#ifdef HAVE_DISCORD
static void discordReady(const DiscordUser* request);
static void discordDisconnected(int errorCode, const char* message);
static void discordErrored(int errorCode, const char* message);
#endif
bool m_DiscordActive;
};

View file

@ -22,6 +22,12 @@ SystemProperties::SystemProperties()
hasBrowser = false; hasBrowser = false;
#endif #endif
#ifdef HAVE_DISCORD
hasDiscordIntegration = true;
#else
hasDiscordIntegration = false;
#endif
unmappedGamepads = SdlInputHandler::getUnmappedGamepads(); unmappedGamepads = SdlInputHandler::getUnmappedGamepads();
// Populate data that requires talking to SDL. We do it all in one shot // Populate data that requires talking to SDL. We do it all in one shot

View file

@ -15,6 +15,7 @@ public:
Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT) Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT)
Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT) Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT)
Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT) Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT)
Q_PROPERTY(bool hasDiscordIntegration MEMBER hasDiscordIntegration CONSTANT)
Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged) Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged)
Q_PROPERTY(int maximumStreamingFrameRate MEMBER maximumStreamingFrameRate CONSTANT) Q_PROPERTY(int maximumStreamingFrameRate MEMBER maximumStreamingFrameRate CONSTANT)
@ -32,6 +33,7 @@ private:
bool isRunningXWayland; bool isRunningXWayland;
bool isWow64; bool isWow64;
bool hasBrowser; bool hasBrowser;
bool hasDiscordIntegration;
QString unmappedGamepads; QString unmappedGamepads;
int maximumStreamingFrameRate; int maximumStreamingFrameRate;
QList<QRect> monitorDesktopResolutions; QList<QRect> monitorDesktopResolutions;

View file

@ -499,6 +499,22 @@ Flickable {
StreamingPreferences.connectionWarnings = checked StreamingPreferences.connectionWarnings = checked
} }
} }
CheckBox {
visible: SystemProperties.hasDiscordIntegration
id: discordPresenceCheck
text: "Discord Rich Presence integration"
font.pointSize: 12
checked: StreamingPreferences.richPresence
onCheckedChanged: {
StreamingPreferences.richPresence = checked
}
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: "Updates your Discord status to display the name of the game you're streaming."
}
} }
} }
} }

View file

@ -23,6 +23,7 @@
#define SER_STARTWINDOWED "startwindowed" #define SER_STARTWINDOWED "startwindowed"
#define SER_FRAMEPACING "framepacing" #define SER_FRAMEPACING "framepacing"
#define SER_CONNWARNINGS "connwarnings" #define SER_CONNWARNINGS "connwarnings"
#define SER_RICHPRESENCE "richpresence"
StreamingPreferences::StreamingPreferences(QObject *parent) StreamingPreferences::StreamingPreferences(QObject *parent)
: QObject(parent) : QObject(parent)
@ -55,6 +56,7 @@ void StreamingPreferences::reload()
startWindowed = settings.value(SER_STARTWINDOWED, false).toBool(); startWindowed = settings.value(SER_STARTWINDOWED, false).toBool();
framePacing = settings.value(SER_FRAMEPACING, false).toBool(); framePacing = settings.value(SER_FRAMEPACING, false).toBool();
connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool(); connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool();
richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG, audioConfig = static_cast<AudioConfig>(settings.value(SER_AUDIOCFG,
static_cast<int>(AudioConfig::AC_STEREO)).toInt()); static_cast<int>(AudioConfig::AC_STEREO)).toInt());
videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG, videoCodecConfig = static_cast<VideoCodecConfig>(settings.value(SER_VIDEOCFG,
@ -86,6 +88,7 @@ void StreamingPreferences::save()
settings.setValue(SER_STARTWINDOWED, startWindowed); settings.setValue(SER_STARTWINDOWED, startWindowed);
settings.setValue(SER_FRAMEPACING, framePacing); settings.setValue(SER_FRAMEPACING, framePacing);
settings.setValue(SER_CONNWARNINGS, connectionWarnings); settings.setValue(SER_CONNWARNINGS, connectionWarnings);
settings.setValue(SER_RICHPRESENCE, richPresence);
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig)); settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig)); settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection)); settings.setValue(SER_VIDEODEC, static_cast<int>(videoDecoderSelection));

View file

@ -64,6 +64,7 @@ public:
Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged) Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged)
Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged) Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged)
Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged) Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged)
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged);
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged) Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged) Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged) Q_PROPERTY(VideoDecoderSelection videoDecoderSelection MEMBER videoDecoderSelection NOTIFY videoDecoderSelectionChanged)
@ -86,6 +87,7 @@ public:
bool startWindowed; bool startWindowed;
bool framePacing; bool framePacing;
bool connectionWarnings; bool connectionWarnings;
bool richPresence;
AudioConfig audioConfig; AudioConfig audioConfig;
VideoCodecConfig videoCodecConfig; VideoCodecConfig videoCodecConfig;
VideoDecoderSelection videoDecoderSelection; VideoDecoderSelection videoDecoderSelection;
@ -110,5 +112,6 @@ signals:
void startWindowedChanged(); void startWindowedChanged();
void framePacingChanged(); void framePacingChanged();
void connectionWarningsChanged(); void connectionWarningsChanged();
void richPresenceChanged();
}; };

View file

@ -1,6 +1,7 @@
#include "session.h" #include "session.h"
#include "settings/streamingpreferences.h" #include "settings/streamingpreferences.h"
#include "streaming/streamutils.h" #include "streaming/streamutils.h"
#include "backend/richpresencemanager.h"
#include <Limelight.h> #include <Limelight.h>
#include <SDL.h> #include <SDL.h>
@ -1047,6 +1048,9 @@ void Session::exec(int displayOriginX, int displayOriginY)
// (m_UnexpectedTermination is set back to true). // (m_UnexpectedTermination is set back to true).
m_UnexpectedTermination = false; m_UnexpectedTermination = false;
// Start rich presence to indicate we're in game
RichPresenceManager presence(prefs, m_App.name);
// Hijack this thread to be the SDL main thread. We have to do this // Hijack this thread to be the SDL main thread. We have to do this
// because we want to suspend all Qt processing until the stream is over. // because we want to suspend all Qt processing until the stream is over.
SDL_Event event; SDL_Event event;
@ -1063,6 +1067,7 @@ void Session::exec(int displayOriginX, int displayOriginY)
// ARM core in the Steam Link, so we will wait 10 ms instead. // ARM core in the Steam Link, so we will wait 10 ms instead.
SDL_Delay(10); SDL_Delay(10);
#endif #endif
presence.runCallbacks();
continue; continue;
} }
switch (event.type) { switch (event.type) {

View file

@ -0,0 +1,26 @@
#pragma once
#if defined(DISCORD_DYNAMIC_LIB)
#if defined(_WIN32)
#if defined(DISCORD_BUILDING_SDK)
#define DISCORD_EXPORT __declspec(dllexport)
#else
#define DISCORD_EXPORT __declspec(dllimport)
#endif
#else
#define DISCORD_EXPORT __attribute__((visibility("default")))
#endif
#else
#define DISCORD_EXPORT
#endif
#ifdef __cplusplus
extern "C" {
#endif
DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command);
DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,87 @@
#pragma once
#include <stdint.h>
// clang-format off
#if defined(DISCORD_DYNAMIC_LIB)
# if defined(_WIN32)
# if defined(DISCORD_BUILDING_SDK)
# define DISCORD_EXPORT __declspec(dllexport)
# else
# define DISCORD_EXPORT __declspec(dllimport)
# endif
# else
# define DISCORD_EXPORT __attribute__((visibility("default")))
# endif
#else
# define DISCORD_EXPORT
#endif
// clang-format on
#ifdef __cplusplus
extern "C" {
#endif
typedef struct DiscordRichPresence {
const char* state; /* max 128 bytes */
const char* details; /* max 128 bytes */
int64_t startTimestamp;
int64_t endTimestamp;
const char* largeImageKey; /* max 32 bytes */
const char* largeImageText; /* max 128 bytes */
const char* smallImageKey; /* max 32 bytes */
const char* smallImageText; /* max 128 bytes */
const char* partyId; /* max 128 bytes */
int partySize;
int partyMax;
const char* matchSecret; /* max 128 bytes */
const char* joinSecret; /* max 128 bytes */
const char* spectateSecret; /* max 128 bytes */
int8_t instance;
} DiscordRichPresence;
typedef struct DiscordUser {
const char* userId;
const char* username;
const char* discriminator;
const char* avatar;
} DiscordUser;
typedef struct DiscordEventHandlers {
void (*ready)(const DiscordUser* request);
void (*disconnected)(int errorCode, const char* message);
void (*errored)(int errorCode, const char* message);
void (*joinGame)(const char* joinSecret);
void (*spectateGame)(const char* spectateSecret);
void (*joinRequest)(const DiscordUser* request);
} DiscordEventHandlers;
#define DISCORD_REPLY_NO 0
#define DISCORD_REPLY_YES 1
#define DISCORD_REPLY_IGNORE 2
DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers,
int autoRegister,
const char* optionalSteamId);
DISCORD_EXPORT void Discord_Shutdown(void);
/* checks for incoming messages, dispatches callbacks */
DISCORD_EXPORT void Discord_RunCallbacks(void);
/* If you disable the lib starting its own io thread, you'll need to call this from your own */
#ifdef DISCORD_DISABLE_IO_THREAD
DISCORD_EXPORT void Discord_UpdateConnection(void);
#endif
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence);
DISCORD_EXPORT void Discord_ClearPresence(void);
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
#ifdef __cplusplus
} /* extern "C" */
#endif

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.