diff --git a/3rdparty/discord-rpc/CMakeLists.txt b/3rdparty/discord-rpc/CMakeLists.txt index b86973762..a8e5b8c45 100644 --- a/3rdparty/discord-rpc/CMakeLists.txt +++ b/3rdparty/discord-rpc/CMakeLists.txt @@ -37,5 +37,6 @@ if(WIN32) target_link_libraries(discord-rpc PRIVATE psapi advapi32) endif() -target_include_directories(discord-rpc SYSTEM PRIVATE ${RapidJSON_INCLUDE_DIRS}) +target_link_libraries(discord-rpc PRIVATE Qt${QT_VERSION_MAJOR}::Core) + target_include_directories(discord-rpc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/3rdparty/discord-rpc/discord_register.h b/3rdparty/discord-rpc/discord_register.h index 4e82fe9a4..2b175dd34 100644 --- a/3rdparty/discord-rpc/discord_register.h +++ b/3rdparty/discord-rpc/discord_register.h @@ -24,11 +24,13 @@ #ifndef DISCORD_REGISTER_H #define DISCORD_REGISTER_H +#include + #ifdef __cplusplus extern "C" { #endif -void Discord_Register(const char *applicationId, const char *command); +void Discord_Register(const QString &applicationId, const char *command); #ifdef __cplusplus } diff --git a/3rdparty/discord-rpc/discord_register_linux.cpp b/3rdparty/discord-rpc/discord_register_linux.cpp index 554c7da37..66511d801 100644 --- a/3rdparty/discord-rpc/discord_register_linux.cpp +++ b/3rdparty/discord-rpc/discord_register_linux.cpp @@ -21,9 +21,6 @@ * */ -#include "discord_rpc.h" -#include "discord_register.h" - #include #include #include @@ -32,6 +29,11 @@ #include #include +#include + +#include "discord_rpc.h" +#include "discord_register.h" + namespace { static bool Mkdir(const char *path) { @@ -48,7 +50,7 @@ static bool Mkdir(const char *path) { } // namespace // We want to register games so we can run them from Discord client as discord-:// -extern "C" void Discord_Register(const char *applicationId, const char *command) { +extern "C" void Discord_Register(const QString &applicationId, const char *command) { // Add a desktop file and update some mime handlers so that xdg-open does the right thing. @@ -75,13 +77,13 @@ extern "C" void Discord_Register(const char *applicationId, const char *command) "Categories=Discord;Games;\n" "MimeType=x-scheme-handler/discord-%s;\n"; char desktopFile[2048]{}; - int fileLen = snprintf(desktopFile, sizeof(desktopFile), desktopFileFormat, applicationId, command, applicationId); + int fileLen = snprintf(desktopFile, sizeof(desktopFile), desktopFileFormat, applicationId.toUtf8().constData(), command, applicationId.toUtf8().constData()); if (fileLen <= 0) { return; } char desktopFilename[256]{}; - (void)snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId); + (void)snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId.toUtf8().constData()); char desktopFilePath[1024]{}; (void)snprintf(desktopFilePath, sizeof(desktopFilePath), "%s/.local", home); @@ -111,8 +113,8 @@ extern "C" void Discord_Register(const char *applicationId, const char *command) snprintf(xdgMimeCommand, sizeof(xdgMimeCommand), "xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s", - applicationId, - applicationId); + applicationId.toUtf8().constData(), + applicationId.toUtf8().constData()); if (system(xdgMimeCommand) < 0) { fprintf(stderr, "Failed to register mime handler\n"); } diff --git a/3rdparty/discord-rpc/discord_register_osx.m b/3rdparty/discord-rpc/discord_register_osx.m index 8226da8a4..70ff02829 100644 --- a/3rdparty/discord-rpc/discord_register_osx.m +++ b/3rdparty/discord-rpc/discord_register_osx.m @@ -84,15 +84,17 @@ static void RegisterURL(const char *applicationId) { } -void Discord_Register(const char *applicationId, const char *command) { +void Discord_Register(const QString &applicationId, const char *command) { + + const QByteArray applicationIdData = applicationId.toUtf8(); if (command) { - RegisterCommand(applicationId, command); + RegisterCommand(applicationIdData.constData(), command); } else { // raii lite @autoreleasepool { - RegisterURL(applicationId); + RegisterURL(applicationIdData.constData()); } } diff --git a/3rdparty/discord-rpc/discord_register_win.cpp b/3rdparty/discord-rpc/discord_register_win.cpp index be177b6b7..672319ea2 100644 --- a/3rdparty/discord-rpc/discord_register_win.cpp +++ b/3rdparty/discord-rpc/discord_register_win.cpp @@ -147,10 +147,10 @@ static void Discord_RegisterW(const wchar_t *applicationId, const wchar_t *comma } -extern "C" void Discord_Register(const char *applicationId, const char *command) { +extern "C" void Discord_Register(const QString &applicationId, const char *command) { wchar_t appId[32]{}; - MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); + MultiByteToWideChar(CP_UTF8, 0, applicationId.toUtf8().constData(), -1, appId, 32); wchar_t openCommand[1024]{}; const wchar_t *wcommand = nullptr; diff --git a/3rdparty/discord-rpc/discord_rpc.cpp b/3rdparty/discord-rpc/discord_rpc.cpp index a16db32c3..212f31429 100644 --- a/3rdparty/discord-rpc/discord_rpc.cpp +++ b/3rdparty/discord-rpc/discord_rpc.cpp @@ -27,6 +27,10 @@ #include #include +#include +#include +#include + #include "discord_rpc.h" #include "discord_backoff.h" #include "discord_register.h" @@ -34,6 +38,8 @@ #include "discord_rpc_connection.h" #include "discord_serialization.h" +using namespace Qt::Literals::StringLiterals; + namespace discord_rpc { constexpr size_t MaxMessageSize { 16 * 1024 }; @@ -52,17 +58,19 @@ struct QueuedMessage { } }; -struct User { +class User { + public: + explicit User() {} // snowflake (64bit int), turned into a ascii decimal string, at most 20 chars +1 null // terminator = 21 - char userId[32]; + QString userId; // 32 unicode glyphs is max name size => 4 bytes per glyph in the worst case, +1 for null // terminator = 129 - char username[344]; + QString username; // 4 decimal digits + 1 null terminator = 5 - char discriminator[8]; + QString discriminator; // optional 'a_' + md5 hex digest (32 bytes) + null terminator = 35 - char avatar[128]; + QString avatar; // Rounded way up because I'm paranoid about games breaking from future changes in these sizes }; @@ -75,12 +83,12 @@ static std::atomic_bool GotErrorMessage { false }; static std::atomic_bool WasJoinGame { false }; static std::atomic_bool WasSpectateGame { false }; static std::atomic_bool UpdatePresence { false }; -static char JoinGameSecret[256]; -static char SpectateGameSecret[256]; +static QString JoinGameSecret; +static QString SpectateGameSecret; static int LastErrorCode { 0 }; -static char LastErrorMessage[256]; +static QString LastErrorMessage; static int LastDisconnectErrorCode { 0 }; -static char LastDisconnectErrorMessage[256]; +static QString LastDisconnectErrorMessage; static std::mutex PresenceMutex; static std::mutex HandlerMutex; static QueuedMessage QueuedPresence {}; @@ -152,65 +160,63 @@ static void Discord_UpdateConnection() { // reads for (;;) { - JsonDocument message; - - if (!Connection->Read(message)) { + QJsonDocument json_document; + if (!Connection->Read(json_document)) { break; } - const char *evtName = GetStrMember(&message, "evt"); - const char *nonce = GetStrMember(&message, "nonce"); + const QJsonObject json_object = json_document.object(); + const QString event_name = json_object["evt"_L1].toString(); + const QString nonce = json_object["nonce"_L1].toString(); - if (nonce) { + if (json_object.contains("nonce"_L1)) { // in responses only -- should use to match up response when needed. - if (evtName && strcmp(evtName, "ERROR") == 0) { - auto data = GetObjMember(&message, "data"); - LastErrorCode = GetIntMember(data, "code"); - StringCopy(LastErrorMessage, GetStrMember(data, "message", "")); + if (event_name == "ERROR"_L1) { + const QJsonObject data = json_object["data"_L1].toObject(); + LastErrorCode = data["code"_L1].toInt(); + LastErrorMessage = data["message"_L1].toString(); GotErrorMessage.store(true); } } else { // should have evt == name of event, optional data - if (evtName == nullptr) { + if (event_name.isEmpty()) { continue; } - auto data = GetObjMember(&message, "data"); + const QJsonObject data = json_object["data"_L1].toObject(); - if (strcmp(evtName, "ACTIVITY_JOIN") == 0) { - auto secret = GetStrMember(data, "secret"); - if (secret) { - StringCopy(JoinGameSecret, secret); + if (event_name == "ACTIVITY_JOIN"_L1) { + if (data.contains("secret"_L1)) { + JoinGameSecret = data["secret"_L1].toString(); WasJoinGame.store(true); } } - else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0) { - auto secret = GetStrMember(data, "secret"); - if (secret) { - StringCopy(SpectateGameSecret, secret); + else if (event_name == "ACTIVITY_SPECTATE"_L1) { + if (data.contains("secret"_L1)) { + SpectateGameSecret = data["secret"_L1].toString(); WasSpectateGame.store(true); } } - else if (strcmp(evtName, "ACTIVITY_JOIN_REQUEST") == 0) { - auto user = GetObjMember(data, "user"); - auto userId = GetStrMember(user, "id"); - auto username = GetStrMember(user, "username"); - auto avatar = GetStrMember(user, "avatar"); - auto joinReq = JoinAskQueue.GetNextAddMessage(); - if (userId && username && joinReq) { - StringCopy(joinReq->userId, userId); - StringCopy(joinReq->username, username); - auto discriminator = GetStrMember(user, "discriminator"); - if (discriminator) { - StringCopy(joinReq->discriminator, discriminator); + else if (event_name == "ACTIVITY_JOIN_REQUEST"_L1) { + const QJsonObject user = data["user"_L1].toObject(); + const QString userId = user["id"_L1].toString(); + const QString username = user["username"_L1].toString(); + const QString avatar = user["avatar"_L1].toString(); + const auto joinReq = JoinAskQueue.GetNextAddMessage(); + if (!userId.isEmpty() && !username.isEmpty() && joinReq) { + joinReq->userId = userId; + joinReq->username = username; + const QString discriminator = user["discriminator"_L1].toString(); + if (!discriminator.isEmpty()) { + joinReq->discriminator = discriminator; } - if (avatar) { - StringCopy(joinReq->avatar, avatar); + if (!avatar.isEmpty()) { + joinReq->avatar = avatar; } else { - joinReq->avatar[0] = 0; + joinReq->avatar.clear(); } JoinAskQueue.CommitAdd(); } @@ -278,7 +284,7 @@ static bool DeregisterForEvent(const char *evtName) { } -extern "C" void Discord_Initialize(const char *applicationId, DiscordEventHandlers *handlers, const int autoRegister) { +extern "C" void Discord_Initialize(const QString &applicationId, DiscordEventHandlers *handlers, const int autoRegister) { IoThread = new (std::nothrow) IoThreadHolder(); if (IoThread == nullptr) { @@ -309,37 +315,38 @@ extern "C" void Discord_Initialize(const char *applicationId, DiscordEventHandle } Connection = RpcConnection::Create(applicationId); - Connection->onConnect = [](JsonDocument &readyMessage) { + Connection->onConnect = [](QJsonDocument &readyMessage) { Discord_UpdateHandlers(&QueuedHandlers); if (QueuedPresence.length > 0) { UpdatePresence.exchange(true); SignalIOActivity(); } - auto data = GetObjMember(&readyMessage, "data"); - auto user = GetObjMember(data, "user"); - auto userId = GetStrMember(user, "id"); - auto username = GetStrMember(user, "username"); - auto avatar = GetStrMember(user, "avatar"); - if (userId && username) { - StringCopy(connectedUser.userId, userId); - StringCopy(connectedUser.username, username); - auto discriminator = GetStrMember(user, "discriminator"); - if (discriminator) { - StringCopy(connectedUser.discriminator, discriminator); + const QJsonValue json_object = readyMessage.object(); + auto data = json_object["data"_L1].toObject(); + auto user = data["user"_L1].toObject(); + auto userId = user["id"_L1].toString(); + auto username = user["username"_L1].toString(); + auto avatar = user["avatar"_L1].toString(); + if (!userId.isEmpty() && !username.isEmpty()) { + connectedUser.userId = userId; + connectedUser.username = username; + const QString discriminator = user["discriminator"_L1].toString(); + if (!discriminator.isEmpty()) { + connectedUser.discriminator = discriminator; } - if (avatar) { - StringCopy(connectedUser.avatar, avatar); + if (!avatar.isEmpty()) { + connectedUser.avatar = avatar; } else { - connectedUser.avatar[0] = 0; + connectedUser = User(); } } WasJustConnected.exchange(true); ReconnectTimeMs.reset(); }; - Connection->onDisconnect = [](int err, const char *message) { + Connection->onDisconnect = [](int err, QString &message) { LastDisconnectErrorCode = err; - StringCopy(LastDisconnectErrorMessage, message); + LastDisconnectErrorMessage = message; WasJustDisconnected.exchange(true); UpdateReconnectTime(); }; @@ -368,7 +375,7 @@ extern "C" void Discord_Shutdown(void) { } -extern "C" void Discord_UpdatePresence(const DiscordRichPresence *presence) { +extern "C" void Discord_UpdatePresence(const DiscordRichPresence &presence) { { std::lock_guard guard(PresenceMutex); @@ -380,8 +387,8 @@ extern "C" void Discord_UpdatePresence(const DiscordRichPresence *presence) { } -extern "C" void Discord_ClearPresence(void) { - Discord_UpdatePresence(nullptr); +extern "C" void Discord_ClearPresence() { + Discord_UpdatePresence(); } extern "C" void Discord_Respond(const char *userId, /* DISCORD_REPLY_ */ int reply) { diff --git a/3rdparty/discord-rpc/discord_rpc.h b/3rdparty/discord-rpc/discord_rpc.h index 81382bfb4..d193a57ac 100644 --- a/3rdparty/discord-rpc/discord_rpc.h +++ b/3rdparty/discord-rpc/discord_rpc.h @@ -25,6 +25,7 @@ #define DISCORD_RPC_H #include +#include namespace discord_rpc { @@ -32,40 +33,41 @@ namespace discord_rpc { extern "C" { #endif -typedef struct DiscordRichPresence { +class DiscordRichPresence { + public: int type; - const char *name; /* max 128 bytes */ - 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 */ + QString name; /* max 128 bytes */ + QString state; /* max 128 bytes */ + QString details; /* max 128 bytes */ + qint64 startTimestamp; + qint64 endTimestamp; + QString largeImageKey; /* max 32 bytes */ + QString largeImageText; /* max 128 bytes */ + QString smallImageKey; /* max 32 bytes */ + QString smallImageText; /* max 128 bytes */ + QString partyId; /* max 128 bytes */ int partySize; int partyMax; int partyPrivacy; - const char *matchSecret; /* max 128 bytes */ - const char *joinSecret; /* max 128 bytes */ - const char *spectateSecret; /* max 128 bytes */ - int8_t instance; -} DiscordRichPresence; + QString matchSecret; /* max 128 bytes */ + QString joinSecret; /* max 128 bytes */ + QString spectateSecret; /* max 128 bytes */ + qint8 instance; +}; typedef struct DiscordUser { - const char *userId; - const char *username; - const char *discriminator; - const char *avatar; + const QString userId; + const QString username; + const QString discriminator; + const QString 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 (*disconnected)(int errorCode, const QString &message); + void (*errored)(int errorCode, const QString &message); + void (*joinGame)(const QString &joinSecret); + void (*spectateGame)(const QString &spectateSecret); void (*joinRequest)(const DiscordUser *request); } DiscordEventHandlers; @@ -75,14 +77,14 @@ typedef struct DiscordEventHandlers { #define DISCORD_PARTY_PRIVATE 0 #define DISCORD_PARTY_PUBLIC 1 -void Discord_Initialize(const char *applicationId, DiscordEventHandlers *handlers, const int autoRegister); -void Discord_Shutdown(void); +void Discord_Initialize(const QString &applicationId, DiscordEventHandlers *handlers, const int autoRegister); +void Discord_Shutdown(); // checks for incoming messages, dispatches callbacks -void Discord_RunCallbacks(void); +void Discord_RunCallbacks(); -void Discord_UpdatePresence(const DiscordRichPresence *presence); -void Discord_ClearPresence(void); +void Discord_UpdatePresence(const DiscordRichPresence &presence = DiscordRichPresence()); +void Discord_ClearPresence(); void Discord_Respond(const char *userid, /* DISCORD_REPLY_ */ int reply); diff --git a/3rdparty/discord-rpc/discord_rpc_connection.cpp b/3rdparty/discord-rpc/discord_rpc_connection.cpp index 94dc1c49f..0f56eae3b 100644 --- a/3rdparty/discord-rpc/discord_rpc_connection.cpp +++ b/3rdparty/discord-rpc/discord_rpc_connection.cpp @@ -21,18 +21,24 @@ * */ +#include +#include +#include + #include "discord_rpc_connection.h" #include "discord_serialization.h" +using namespace Qt::Literals::StringLiterals; + namespace discord_rpc { static const int RpcVersion = 1; static RpcConnection Instance; -RpcConnection *RpcConnection::Create(const char *applicationId) { +RpcConnection *RpcConnection::Create(const QString &applicationId) { Instance.connection = BaseConnection::Create(); - StringCopy(Instance.appId, applicationId); + Instance.appId = applicationId; return &Instance; } @@ -56,14 +62,15 @@ void RpcConnection::Open() { } if (state == State::SentHandshake) { - JsonDocument message; - if (Read(message)) { - auto cmd = GetStrMember(&message, "cmd"); - auto evt = GetStrMember(&message, "evt"); - if (cmd && evt && !strcmp(cmd, "DISPATCH") && !strcmp(evt, "READY")) { + QJsonDocument json_document; + if (Read(json_document)) { + const QJsonObject json_object = json_document.object(); + const QString cmd = json_object["cmd"_L1].toString(); + const QString evt = json_object["evt"_L1].toString(); + if (cmd == "DISPATCH"_L1 && evt == "READY"_L1) { state = State::Connected; if (onConnect) { - onConnect(message); + onConnect(json_document); } } } @@ -106,7 +113,7 @@ bool RpcConnection::Write(const void *data, size_t length) { } -bool RpcConnection::Read(JsonDocument &message) { +bool RpcConnection::Read(QJsonDocument &message) { if (state != State::Connected && state != State::SentHandshake) { return false; @@ -117,7 +124,7 @@ bool RpcConnection::Read(JsonDocument &message) { if (!didRead) { if (!connection->isOpen) { lastErrorCode = static_cast(ErrorCode::PipeClosed); - StringCopy(lastErrorMessage, "Pipe closed"); + lastErrorMessage = "Pipe closed"_L1; Close(); } return false; @@ -127,7 +134,7 @@ bool RpcConnection::Read(JsonDocument &message) { didRead = connection->Read(readFrame.message, readFrame.length); if (!didRead) { lastErrorCode = static_cast(ErrorCode::ReadCorrupt); - StringCopy(lastErrorMessage, "Partial data in frame"); + lastErrorMessage = "Partial data in frame"_L1; Close(); return false; } @@ -136,14 +143,14 @@ bool RpcConnection::Read(JsonDocument &message) { switch (readFrame.opcode) { case Opcode::Close: { - message.ParseInsitu(readFrame.message); - lastErrorCode = GetIntMember(&message, "code"); - StringCopy(lastErrorMessage, GetStrMember(&message, "message", "")); + message = QJsonDocument::fromJson(readFrame.message); + lastErrorCode = message["code"_L1].toInt(); + lastErrorMessage = message["message"_L1].toString(); Close(); return false; } case Opcode::Frame: - message.ParseInsitu(readFrame.message); + message = QJsonDocument::fromJson(readFrame.message); return true; case Opcode::Ping: readFrame.opcode = Opcode::Pong; @@ -157,7 +164,7 @@ bool RpcConnection::Read(JsonDocument &message) { default: // something bad happened lastErrorCode = static_cast(ErrorCode::ReadCorrupt); - StringCopy(lastErrorMessage, "Bad ipc frame"); + lastErrorMessage = "Bad ipc frame"_L1; Close(); return false; } diff --git a/3rdparty/discord-rpc/discord_rpc_connection.h b/3rdparty/discord-rpc/discord_rpc_connection.h index 550c8aea5..9bee65dfb 100644 --- a/3rdparty/discord-rpc/discord_rpc_connection.h +++ b/3rdparty/discord-rpc/discord_rpc_connection.h @@ -24,6 +24,9 @@ #ifndef DISCORD_RPC_CONNECTION_H #define DISCORD_RPC_CONNECTION_H +#include +#include + #include "discord_connection.h" #include "discord_serialization.h" @@ -65,14 +68,14 @@ struct RpcConnection { BaseConnection *connection { nullptr }; State state { State::Disconnected }; - void (*onConnect)(JsonDocument &message) { nullptr }; - void (*onDisconnect)(int errorCode, const char *message) { nullptr }; - char appId[64] {}; + void (*onConnect)(QJsonDocument &message) { nullptr }; + void (*onDisconnect)(int errorCode, QString &message) { nullptr }; + QString appId; int lastErrorCode { 0 }; - char lastErrorMessage[256] {}; + QString lastErrorMessage; RpcConnection::MessageFrame sendFrame; - static RpcConnection *Create(const char *applicationId); + static RpcConnection *Create(const QString &applicationId); static void Destroy(RpcConnection *&); inline bool IsOpen() const { return state == State::Connected; } @@ -80,7 +83,7 @@ struct RpcConnection { void Open(); void Close(); bool Write(const void *data, size_t length); - bool Read(JsonDocument &message); + bool Read(QJsonDocument &message); }; } // namespace discord_rpc diff --git a/3rdparty/discord-rpc/discord_serialization.cpp b/3rdparty/discord-rpc/discord_serialization.cpp index d7f1c5b87..93babf0f2 100644 --- a/3rdparty/discord-rpc/discord_serialization.cpp +++ b/3rdparty/discord-rpc/discord_serialization.cpp @@ -21,10 +21,15 @@ * */ +#include +#include +#include + #include "discord_serialization.h" -#include "discord_connection.h" #include "discord_rpc.h" +using namespace Qt::Literals::StringLiterals; + namespace discord_rpc { template @@ -53,229 +58,129 @@ void NumberToString(char *dest, T number) { } -// it's ever so slightly faster to not have to strlen the key -template -void WriteKey(JsonWriter &w, T &k) { - w.Key(k, sizeof(T) - 1); -} +void WriteOptionalString(QJsonObject &json_object, const QString &key, const QString &value); +void WriteOptionalString(QJsonObject &json_object, const QString &key, const QString &value) { -struct WriteObject { - JsonWriter &writer; - WriteObject(JsonWriter &w) - : writer(w) { - writer.StartObject(); - } - template - WriteObject(JsonWriter &w, T &name) - : writer(w) { - WriteKey(writer, name); - writer.StartObject(); - } - ~WriteObject() { writer.EndObject(); } -}; - -struct WriteArray { - JsonWriter &writer; - template - WriteArray(JsonWriter &w, T &name) - : writer(w) { - WriteKey(writer, name); - writer.StartArray(); - } - ~WriteArray() { writer.EndArray(); } -}; - -template -void WriteOptionalString(JsonWriter &w, T &k, const char *value) { - - if (value && value[0]) { - w.Key(k, sizeof(T) - 1); - w.String(value); + if (!value.isEmpty()) { + json_object[key] = value; } } -static void JsonWriteNonce(JsonWriter &writer, const int nonce) { +static QString JsonWriteNonce(const int nonce) { - WriteKey(writer, "nonce"); - char nonceBuffer[32]; - NumberToString(nonceBuffer, nonce); - writer.String(nonceBuffer); + char nonce_buffer[32]{}; + NumberToString(nonce_buffer, nonce); + + return QString::fromLatin1(nonce_buffer); } -size_t JsonWriteRichPresenceObj(char *dest, const size_t maxLen, const int nonce, const int pid, const DiscordRichPresence *presence) { +size_t JsonWriteRichPresenceObj(char *dest, const size_t maxLen, const int nonce, const int pid, const DiscordRichPresence &presence) { - JsonWriter writer(dest, maxLen); + QJsonObject json_object; - { - WriteObject top(writer); + json_object["nonce"_L1] = JsonWriteNonce(nonce); + json_object["cmd"_L1] = "SET_ACTIVITY"_L1; - JsonWriteNonce(writer, nonce); + QJsonObject args; + args["pid"_L1] = pid; - WriteKey(writer, "cmd"); - writer.String("SET_ACTIVITY"); + QJsonObject activity; - { - WriteObject args(writer, "args"); + if (presence.type >= 0 && presence.type <= 5) { + activity["type"_L1] = presence.type; + } - WriteKey(writer, "pid"); - writer.Int(pid); + activity["state"_L1] = presence.state; + activity["details"_L1] = presence.details; - if (presence != nullptr) { - WriteObject activity(writer, "activity"); - - if (presence->type >= 0 && presence->type <= 5) { - WriteKey(writer, "type"); - writer.Int(presence->type); - } - - WriteOptionalString(writer, "name", presence->name); - WriteOptionalString(writer, "state", presence->state); - WriteOptionalString(writer, "details", presence->details); - - if (presence->startTimestamp || presence->endTimestamp) { - WriteObject timestamps(writer, "timestamps"); - - if (presence->startTimestamp) { - WriteKey(writer, "start"); - writer.Int64(presence->startTimestamp); - } - - if (presence->endTimestamp) { - WriteKey(writer, "end"); - writer.Int64(presence->endTimestamp); - } - } - - if ((presence->largeImageKey && presence->largeImageKey[0]) || - (presence->largeImageText && presence->largeImageText[0]) || - (presence->smallImageKey && presence->smallImageKey[0]) || - (presence->smallImageText && presence->smallImageText[0])) { - WriteObject assets(writer, "assets"); - WriteOptionalString(writer, "large_image", presence->largeImageKey); - WriteOptionalString(writer, "large_text", presence->largeImageText); - WriteOptionalString(writer, "small_image", presence->smallImageKey); - WriteOptionalString(writer, "small_text", presence->smallImageText); - } - - if ((presence->partyId && presence->partyId[0]) || presence->partySize || - presence->partyMax || presence->partyPrivacy) { - WriteObject party(writer, "party"); - WriteOptionalString(writer, "id", presence->partyId); - if (presence->partySize && presence->partyMax) { - WriteArray size(writer, "size"); - writer.Int(presence->partySize); - writer.Int(presence->partyMax); - } - - if (presence->partyPrivacy) { - WriteKey(writer, "privacy"); - writer.Int(presence->partyPrivacy); - } - } - - if ((presence->matchSecret && presence->matchSecret[0]) || - (presence->joinSecret && presence->joinSecret[0]) || - (presence->spectateSecret && presence->spectateSecret[0])) { - WriteObject secrets(writer, "secrets"); - WriteOptionalString(writer, "match", presence->matchSecret); - WriteOptionalString(writer, "join", presence->joinSecret); - WriteOptionalString(writer, "spectate", presence->spectateSecret); - } - - writer.Key("instance"); - writer.Bool(presence->instance != 0); - } + if (presence.startTimestamp != 0 || presence.endTimestamp != 0) { + QJsonObject timestamps; + if (presence.startTimestamp != 0) { + timestamps["start"_L1] = presence.startTimestamp; } + if (presence.endTimestamp != 0) { + timestamps["end"_L1] = presence.endTimestamp; + } + activity["timestamps"_L1] = timestamps; } - return writer.Size(); -} - -size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char *applicationId) { - - JsonWriter writer(dest, maxLen); - - { - WriteObject obj(writer); - WriteKey(writer, "v"); - writer.Int(version); - WriteKey(writer, "client_id"); - writer.String(applicationId); + if (!presence.largeImageKey.isEmpty() || !presence.largeImageText.isEmpty() || !presence.smallImageKey.isEmpty() || !presence.smallImageText.isEmpty()) { + QJsonObject assets; + WriteOptionalString(assets, "large_image"_L1, presence.largeImageKey); + WriteOptionalString(assets, "large_text"_L1, presence.largeImageText); + WriteOptionalString(assets, "small_image"_L1, presence.smallImageKey); + WriteOptionalString(assets, "small_text"_L1, presence.smallImageText); + activity["assets"_L1] = assets; } - return writer.Size(); + activity["instance"_L1] = presence.instance != 0; + args["activity"_L1] = activity; + json_object["args"_L1] = args; + + QJsonDocument json_document(json_object); + QByteArray data = json_document.toJson(QJsonDocument::Compact); + strncpy(dest, data.constData(), maxLen); + + return data.length(); } -size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName) { +size_t JsonWriteHandshakeObj(char *dest, const size_t maxLen, const int version, const QString &applicationId) { - JsonWriter writer(dest, maxLen); + QJsonObject json_object; + json_object["v"_L1] = version; + json_object["client_id"_L1] = applicationId; + const QJsonDocument json_document(json_object); + const QByteArray data = json_document.toJson(QJsonDocument::Compact); + strncpy(dest, data.constData(), maxLen); - { - WriteObject obj(writer); - - JsonWriteNonce(writer, nonce); - - WriteKey(writer, "cmd"); - writer.String("SUBSCRIBE"); - - WriteKey(writer, "evt"); - writer.String(evtName); - } - - return writer.Size(); + return data.length(); } -size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName) { +size_t JsonWriteSubscribeCommand(char *dest, const size_t maxLen, const int nonce, const char *evtName) { - JsonWriter writer(dest, maxLen); + QJsonObject json_object; + json_object["nonce"_L1] = JsonWriteNonce(nonce); + json_object["cmd"_L1] = "SUBSCRIBE"_L1; + json_object["evt"_L1] = QLatin1String(evtName); + const QJsonDocument json_document(json_object); + const QByteArray data = json_document.toJson(QJsonDocument::Compact); + strncpy(dest, data.constData(), maxLen); - { - WriteObject obj(writer); + return data.length(); - JsonWriteNonce(writer, nonce); +} - WriteKey(writer, "cmd"); - writer.String("UNSUBSCRIBE"); +size_t JsonWriteUnsubscribeCommand(char *dest, const size_t maxLen, const int nonce, const char *evtName) { - WriteKey(writer, "evt"); - writer.String(evtName); - } + QJsonObject json_object; + json_object["nonce"_L1] = JsonWriteNonce(nonce); + json_object["cmd"_L1] = "UNSUBSCRIBE"_L1; + json_object["evt"_L1] = QLatin1String(evtName); + const QJsonDocument json_document(json_object); + const QByteArray data = json_document.toJson(QJsonDocument::Compact); + strncpy(dest, data.constData(), maxLen); - return writer.Size(); + return data.length(); } size_t JsonWriteJoinReply(char *dest, size_t maxLen, const char *userId, const int reply, const int nonce) { - JsonWriter writer(dest, maxLen); + QJsonObject json_object; + json_object["nonce"_L1] = JsonWriteNonce(nonce); + json_object["cmd"_L1] = reply == DISCORD_REPLY_YES ? "SEND_ACTIVITY_JOIN_INVITE"_L1 : "CLOSE_ACTIVITY_JOIN_REQUEST"_L1; + QJsonObject args; + args["user_id"_L1] = QLatin1String(userId); + json_object["args"_L1] = args; + const QJsonDocument json_document(json_object); + const QByteArray data = json_document.toJson(QJsonDocument::Compact); + strncpy(dest, data.constData(), maxLen); - { - WriteObject obj(writer); - - WriteKey(writer, "cmd"); - if (reply == DISCORD_REPLY_YES) { - writer.String("SEND_ACTIVITY_JOIN_INVITE"); - } - else { - writer.String("CLOSE_ACTIVITY_JOIN_REQUEST"); - } - - WriteKey(writer, "args"); - { - WriteObject args(writer); - - WriteKey(writer, "user_id"); - writer.String(userId); - } - - JsonWriteNonce(writer, nonce); - } - - return writer.Size(); + return data.length(); } diff --git a/3rdparty/discord-rpc/discord_serialization.h b/3rdparty/discord-rpc/discord_serialization.h index 65c6e05eb..b008e595a 100644 --- a/3rdparty/discord-rpc/discord_serialization.h +++ b/3rdparty/discord-rpc/discord_serialization.h @@ -24,189 +24,18 @@ #ifndef DISCORD_SERIALIZATION_H #define DISCORD_SERIALIZATION_H -#include -#include -#include +#include +#include namespace discord_rpc { -// if only there was a standard library function for this -template -inline size_t StringCopy(char (&dest)[Len], const char *src) { - if (!src || !Len) { - return 0; - } - size_t copied; - char *out = dest; - for (copied = 1; *src && copied < Len; ++copied) { - *out++ = *src++; - } - *out = 0; - return copied - 1; -} - -size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char *applicationId); - -// Commands -struct DiscordRichPresence; -size_t JsonWriteRichPresenceObj(char *dest, const size_t maxLen, const int nonce, const int pid, const DiscordRichPresence *presence); +size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const QString &applicationId); +class DiscordRichPresence; +size_t JsonWriteRichPresenceObj(char *dest, const size_t maxLen, const int nonce, const int pid, const DiscordRichPresence &presence); size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName); - size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName); - size_t JsonWriteJoinReply(char *dest, size_t maxLen, const char *userId, int reply, int nonce); -// I want to use as few allocations as I can get away with, and to do that with RapidJson, you need -// to supply some of your own allocators for stuff rather than use the defaults - -class LinearAllocator { - public: - char *buffer_; - char *end_; - LinearAllocator() { - assert(0); // needed for some default case in rapidjson, should not use - } - LinearAllocator(char *buffer, size_t size) - : buffer_(buffer), end_(buffer + size) { - } - static const bool kNeedFree = false; - void *Malloc(size_t size) { - char *res = buffer_; - buffer_ += size; - if (buffer_ > end_) { - buffer_ = res; - return nullptr; - } - return res; - } - void *Realloc(void *originalPtr, size_t originalSize, size_t newSize) { - if (newSize == 0) { - return nullptr; - } - // allocate how much you need in the first place - assert(!originalPtr && !originalSize); - // unused parameter warning - (void)(originalPtr); - (void)(originalSize); - return Malloc(newSize); - } - static void Free(void *ptr) { - /* shrug */ - (void)ptr; - } -}; - -template -class FixedLinearAllocator : public LinearAllocator { - public: - char fixedBuffer_[Size]; - FixedLinearAllocator() - : LinearAllocator(fixedBuffer_, Size) { - } - static const bool kNeedFree = false; -}; - -// wonder why this isn't a thing already, maybe I missed it -class DirectStringBuffer { - public: - using Ch = char; - char *buffer_; - char *end_; - char *current_; - - DirectStringBuffer(char *buffer, size_t maxLen) - : buffer_(buffer), end_(buffer + maxLen), current_(buffer) { - } - - void Put(char c) { - if (current_ < end_) { - *current_++ = c; - } - } - void Flush() {} - size_t GetSize() const { return static_cast(current_ - buffer_); } -}; - -using MallocAllocator = rapidjson::CrtAllocator; -using PoolAllocator = rapidjson::MemoryPoolAllocator; -using UTF8 = rapidjson::UTF8; -// Writer appears to need about 16 bytes per nested object level (with 64bit size_t) -using StackAllocator = FixedLinearAllocator<2048>; -constexpr size_t WriterNestingLevels = 2048 / (2 * sizeof(size_t)); -using JsonWriterBase = - rapidjson::Writer; -class JsonWriter : public JsonWriterBase { - public: - DirectStringBuffer stringBuffer_; - StackAllocator stackAlloc_; - - JsonWriter(char *dest, size_t maxLen) - : JsonWriterBase(stringBuffer_, &stackAlloc_, WriterNestingLevels), stringBuffer_(dest, maxLen), stackAlloc_() { - } - - size_t Size() const { return stringBuffer_.GetSize(); } -}; - -using JsonDocumentBase = rapidjson::GenericDocument; -class JsonDocument : public JsonDocumentBase { - public: - static const int kDefaultChunkCapacity = 32 * 1024; - // json parser will use this buffer first, then allocate more if needed; I seriously doubt we - // send any messages that would use all of this, though. - char parseBuffer_[32 * 1024]; - MallocAllocator mallocAllocator_; - PoolAllocator poolAllocator_; - StackAllocator stackAllocator_; - JsonDocument() - : JsonDocumentBase(rapidjson::kObjectType, - &poolAllocator_, - sizeof(stackAllocator_.fixedBuffer_), - &stackAllocator_), - poolAllocator_(parseBuffer_, sizeof(parseBuffer_), kDefaultChunkCapacity, &mallocAllocator_), stackAllocator_() { - } -}; - -using JsonValue = rapidjson::GenericValue; - -inline JsonValue *GetObjMember(JsonValue *obj, const char *name) { - - if (obj) { - auto member = obj->FindMember(name); - if (member != obj->MemberEnd() && member->value.IsObject()) { - return &member->value; - } - } - - return nullptr; - -} - -inline int GetIntMember(JsonValue *obj, const char *name, int notFoundDefault = 0) { - - if (obj) { - auto member = obj->FindMember(name); - if (member != obj->MemberEnd() && member->value.IsInt()) { - return member->value.GetInt(); - } - } - - return notFoundDefault; - -} - -inline const char *GetStrMember(JsonValue *obj, const char *name, const char *notFoundDefault = nullptr) { - - if (obj) { - auto member = obj->FindMember(name); - if (member != obj->MemberEnd() && member->value.IsString()) { - return member->value.GetString(); - } - } - - return notFoundDefault; - -} - } // namespace discord_rpc #endif // DISCORD_SERIALIZATION_H diff --git a/CMakeLists.txt b/CMakeLists.txt index b9c044857..7a4575dfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -212,8 +212,6 @@ find_package(GTest) pkg_check_modules(LIBSPARSEHASH IMPORTED_TARGET libsparsehash) -find_package(RapidJSON) - set(QT_VERSION_MAJOR 6) set(QT_MIN_VERSION 6.4.0) set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION_MAJOR}) @@ -366,9 +364,7 @@ optional_component(STREAMTAGREADER ON "Stream tagreader" DEPENDS "sparsehash" LIBSPARSEHASH_FOUND ) -optional_component(DISCORD_RPC ON "Discord Rich Presence" - DEPENDS "RapidJSON" RapidJSON_FOUND -) +optional_component(DISCORD_RPC ON "Discord Rich Presence") if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ) set(HAVE_CHROMAPRINT ON) diff --git a/README.md b/README.md index 73d02c22b..e4555e6f4 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,6 @@ Optional dependencies: * MTP devices: [libmtp](http://libmtp.sourceforge.net/) * iPod Classic devices: [libgpod](http://www.gtkpod.org/libgpod/) * EBU R 128 loudness normalization [libebur128](https://github.com/jiixyj/libebur128) -* Discord rich presence [RapidJSON](https://rapidjson.org/) You should also install the gstreamer plugins base and good, and optionally bad, ugly and libav to support all audio formats. diff --git a/src/discord/richpresence.cpp b/src/discord/richpresence.cpp index 57b2bde66..052ed7373 100644 --- a/src/discord/richpresence.cpp +++ b/src/discord/richpresence.cpp @@ -50,7 +50,7 @@ RichPresence::RichPresence(const SharedPtr player, playlist_manager_(playlist_manager), enabled_(false) { - Discord_Initialize(kDiscordApplicationId, nullptr, 1); + Discord_Initialize(QLatin1String(kDiscordApplicationId), nullptr, 1); QObject::connect(&*player_->engine(), &EngineBase::StateChanged, this, &RichPresence::EngineStateChanged); QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentSongChanged, this, &RichPresence::CurrentSongChanged); @@ -109,36 +109,33 @@ void RichPresence::SendPresenceUpdate() { return; } - ::DiscordRichPresence presence_data{}; - memset(&presence_data, 0, sizeof(presence_data)); + DiscordRichPresence presence_data{}; presence_data.type = 2; // Listening - presence_data.largeImageKey = kStrawberryIconResourceName; - presence_data.smallImageKey = kStrawberryIconResourceName; - presence_data.smallImageText = kStrawberryIconDescription; + presence_data.largeImageKey = QLatin1String(kStrawberryIconResourceName); + presence_data.smallImageKey = QLatin1String(kStrawberryIconResourceName); + presence_data.smallImageText = QLatin1String(kStrawberryIconDescription); presence_data.instance = 0; - QByteArray artist; if (!activity_.artist.isEmpty()) { - artist = activity_.artist.toUtf8(); - artist.prepend(tr("by ").toUtf8()); - presence_data.state = artist.constData(); + QString artist = activity_.artist; + artist.prepend(tr("by ")); + presence_data.state = artist; } - QByteArray album; if (!activity_.album.isEmpty() && activity_.album != activity_.title) { - album = activity_.album.toUtf8(); - album.prepend(tr("on ").toUtf8()); - presence_data.largeImageText = album.constData(); + QString album = activity_.album; + album.prepend(tr("on ")); + presence_data.largeImageText = album; } - const QByteArray title = activity_.title.toUtf8(); - presence_data.details = title.constData(); + const QString title = activity_.title; + presence_data.details = title; const qint64 start_timestamp = activity_.start_timestamp - activity_.seek_secs; presence_data.startTimestamp = start_timestamp; presence_data.endTimestamp = start_timestamp + activity_.length_secs; - Discord_UpdatePresence(&presence_data); + Discord_UpdatePresence(presence_data); }