(Discord RPC) Update

This commit is contained in:
twinaphex 2020-05-22 04:56:53 +02:00
parent 3cd320fa83
commit a6030f89fe
9 changed files with 236 additions and 249 deletions

View File

@ -53,9 +53,9 @@ typedef struct DiscordEventHandlers
#define DISCORD_REPLY_IGNORE 2
DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers,
int autoRegister,
const char* optionalSteamId);
DiscordEventHandlers* handlers,
int autoRegister,
const char* optionalSteamId);
DISCORD_EXPORT void Discord_Shutdown(void);
/* checks for incoming messages, dispatches callbacks */

View File

@ -5,37 +5,36 @@
#include <stdint.h>
#include <time.h>
struct Backoff
{
int64_t minAmount;
int64_t maxAmount;
int64_t current;
int fails;
std::mt19937_64 randGenerator;
std::uniform_real_distribution<> randDistribution;
struct Backoff {
int64_t minAmount;
int64_t maxAmount;
int64_t current;
int fails;
std::mt19937_64 randGenerator;
std::uniform_real_distribution<> randDistribution;
double rand01() { return randDistribution(randGenerator); }
double rand01() { return randDistribution(randGenerator); }
Backoff(int64_t min, int64_t max)
Backoff(int64_t min, int64_t max)
: minAmount(min)
, maxAmount(max)
, current(min)
, fails(0)
, randGenerator((uint64_t)time(0))
{
}
, maxAmount(max)
, current(min)
, fails(0)
, randGenerator((uint64_t)time(0))
{
}
void reset()
{
fails = 0;
current = minAmount;
}
void reset()
{
fails = 0;
current = minAmount;
}
int64_t nextDelay()
{
++fails;
int64_t delay = (int64_t)((double)current * 2.0 * rand01());
current = std::min(current + delay, maxAmount);
return current;
}
int64_t nextDelay()
{
++fails;
int64_t delay = (int64_t)((double)current * 2.0 * rand01());
current = std::min(current + delay, maxAmount);
return current;
}
};

View File

@ -6,15 +6,14 @@
#include <stdlib.h>
/* not really connectiony, but need per-platform */
int GetProcessId(void);
int GetProcessId();
struct BaseConnection
{
static BaseConnection* Create();
static void Destroy(BaseConnection*&);
bool isOpen{false};
bool Open();
bool Close();
bool Write(const void* data, size_t length);
bool Read(void* data, size_t length);
struct BaseConnection {
static BaseConnection* Create();
static void Destroy(BaseConnection*&);
bool isOpen{false};
bool Open();
bool Close();
bool Write(const void* data, size_t length);
bool Read(void* data, size_t length);
};

View File

@ -1,3 +1,5 @@
#include "discord_rpc.h"
#include "discord_register.h"
#include <stdio.h>
#include <errno.h>
@ -11,11 +13,9 @@
#include <file/file_path.h>
#include <compat/strl.h>
#include <discord_rpc.h>
/* we want to register games so we can run them from
* Discord client as discord-<appid>:// */
void Discord_Register(const char *applicationId, const char *command)
void Discord_Register(const char* applicationId, const char* command)
{
FILE* fp;
int fileLen;
@ -82,7 +82,9 @@ void Discord_Register(const char *applicationId, const char *command)
fprintf(stderr, "Failed to register mime handler\n");
}
void Discord_RegisterSteamGame(const char *applicationId, const char *steamId)
void Discord_RegisterSteamGame(
const char* applicationId,
const char* steamId)
{
char command[256];
snprintf(command, sizeof(command), "xdg-open steam://rungameid/%s", steamId);

View File

@ -3,6 +3,8 @@
#import <AppKit/AppKit.h>
#include "../include/discord_register.h"
static void RegisterCommand(const char* applicationId, const char* command)
{
/* There does not appear to be a way to register arbitrary commands on OSX, so instead we'll save the command
@ -29,52 +31,54 @@ static void RegisterCommand(const char* applicationId, const char* command)
static void RegisterURL(const char* applicationId)
{
char url[256];
snprintf(url, sizeof(url), "discord-%s", applicationId);
CFStringRef cfURL = CFStringCreateWithCString(NULL, url, kCFStringEncodingUTF8);
NSString* myBundleId = [[NSBundle mainBundle] bundleIdentifier];
char url[256];
snprintf(url, sizeof(url), "discord-%s", applicationId);
CFStringRef cfURL = CFStringCreateWithCString(NULL, url, kCFStringEncodingUTF8);
NSString* myBundleId = [[NSBundle mainBundle] bundleIdentifier];
if (!myBundleId)
{
fprintf(stderr, "No bundle id found\n");
return;
}
if (!myBundleId)
{
fprintf(stderr, "No bundle id found\n");
return;
}
NSURL* myURL = [[NSBundle mainBundle] bundleURL];
if (!myURL)
{
fprintf(stderr, "No bundle url found\n");
return;
}
NSURL* myURL = [[NSBundle mainBundle] bundleURL];
if (!myURL)
{
fprintf(stderr, "No bundle url found\n");
return;
}
OSStatus status = LSSetDefaultHandlerForURLScheme(cfURL, (__bridge CFStringRef)myBundleId);
if (status != noErr)
{
fprintf(stderr, "Error in LSSetDefaultHandlerForURLScheme: %d\n", (int)status);
return;
}
OSStatus status = LSSetDefaultHandlerForURLScheme(cfURL, (__bridge CFStringRef)myBundleId);
if (status != noErr)
{
fprintf(stderr, "Error in LSSetDefaultHandlerForURLScheme: %d\n", (int)status);
return;
}
status = LSRegisterURL((__bridge CFURLRef)myURL, true);
if (status != noErr)
fprintf(stderr, "Error in LSRegisterURL: %d\n", (int)status);
status = LSRegisterURL((__bridge CFURLRef)myURL, true);
if (status != noErr)
{
fprintf(stderr, "Error in LSRegisterURL: %d\n", (int)status);
}
}
void Discord_Register(const char* applicationId, const char* command)
{
if (command)
RegisterCommand(applicationId, command);
else
{
/* RAII Lite */
@autoreleasepool {
RegisterURL(applicationId);
}
}
if (command)
RegisterCommand(applicationId, command);
else
{
/* RAII Lite */
@autoreleasepool {
RegisterURL(applicationId);
}
}
}
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
{
char command[256];
snprintf(command, sizeof(command), "steam://rungameid/%s", steamId);
Discord_Register(applicationId, command);
char command[256];
snprintf(command, sizeof(command), "steam://rungameid/%s", steamId);
Discord_Register(applicationId, command);
}

View File

@ -1,4 +1,5 @@
#include "discord_rpc.h"
#include "discord_register.h"
#define WIN32_LEAN_AND_MEAN
#define NOMCX
@ -52,99 +53,99 @@ static LSTATUS regset(HKEY hkey,
const void* data,
DWORD len)
{
LSTATUS ret;
HKEY htkey = hkey, hsubkey = NULL;
if (subkey && subkey[0])
{
if ((ret = RegCreateKeyExW(hkey, subkey, 0, 0, 0, KEY_ALL_ACCESS, 0, &hsubkey, 0)) !=
HKEY htkey = hkey, hsubkey = NULL;
LSTATUS ret;
if (subkey && subkey[0])
{
if ((ret = RegCreateKeyExW(hkey, subkey, 0, 0, 0, KEY_ALL_ACCESS, 0, &hsubkey, 0)) !=
ERROR_SUCCESS)
return ret;
htkey = hsubkey;
}
ret = RegSetValueExW(htkey, name, 0, type, (const BYTE*)data, len);
if (hsubkey && hsubkey != hkey)
RegCloseKey(hsubkey);
return ret;
return ret;
htkey = hsubkey;
}
ret = RegSetValueExW(htkey, name, 0, type, (const BYTE*)data, len);
if (hsubkey && hsubkey != hkey)
RegCloseKey(hsubkey);
return ret;
}
static void Discord_RegisterW(
const wchar_t* applicationId, const wchar_t* command)
{
/* https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx
* we want to register games so we can run them as discord-<appid>://
* Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions. */
/* https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx
* we want to register games so we can run them as discord-<appid>://
* Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions. */
DWORD len;
LSTATUS result;
wchar_t urlProtocol = 0;
wchar_t keyName[256];
wchar_t protocolName[64];
wchar_t protocolDescription[128];
wchar_t exeFilePath[MAX_PATH];
DWORD exeLen = GetModuleFileNameW(NULL, exeFilePath, MAX_PATH);
wchar_t openCommand[1024];
DWORD len;
LSTATUS result;
wchar_t urlProtocol = 0;
wchar_t keyName[256];
wchar_t protocolName[64];
wchar_t protocolDescription[128];
wchar_t exeFilePath[MAX_PATH];
DWORD exeLen = GetModuleFileNameW(NULL, exeFilePath, MAX_PATH);
wchar_t openCommand[1024];
if (command && command[0])
StringCbPrintfW(openCommand, sizeof(openCommand), L"%S", command);
else
StringCbPrintfW(openCommand, sizeof(openCommand), L"%S", exeFilePath);
if (command && command[0])
StringCbPrintfW(openCommand, sizeof(openCommand), L"%S", command);
else
StringCbPrintfW(openCommand, sizeof(openCommand), L"%S", exeFilePath);
StringCbPrintfW(protocolName, sizeof(protocolName),
L"discord-%S", applicationId);
StringCbPrintfW(
protocolDescription, sizeof(protocolDescription),
L"URL:Run game %S protocol", applicationId);
StringCbPrintfW(keyName, sizeof(keyName), L"Software\\Classes\\%S", protocolName);
HKEY key;
LSTATUS status =
StringCbPrintfW(protocolName, sizeof(protocolName),
L"discord-%S", applicationId);
StringCbPrintfW(
protocolDescription, sizeof(protocolDescription),
L"URL:Run game %S protocol", applicationId);
StringCbPrintfW(keyName, sizeof(keyName), L"Software\\Classes\\%S", protocolName);
HKEY key;
LSTATUS status =
RegCreateKeyExW(HKEY_CURRENT_USER, keyName, 0, NULL, 0, KEY_WRITE, NULL, &key, NULL);
if (status != ERROR_SUCCESS)
{
fprintf(stderr, "Error creating key\n");
return;
}
len = (DWORD)lstrlenW(protocolDescription) + 1;
result =
if (status != ERROR_SUCCESS)
{
fprintf(stderr, "Error creating key\n");
return;
}
len = (DWORD)lstrlenW(protocolDescription) + 1;
result =
RegSetKeyValueW(key, NULL, NULL, REG_SZ, protocolDescription, len * sizeof(wchar_t));
if (FAILED(result))
fprintf(stderr, "Error writing description\n");
if (FAILED(result)) {
fprintf(stderr, "Error writing description\n");
}
len = (DWORD)lstrlenW(protocolDescription) + 1;
result = RegSetKeyValueW(key, NULL, L"URL Protocol", REG_SZ, &urlProtocol, sizeof(wchar_t));
if (FAILED(result))
fprintf(stderr, "Error writing description\n");
len = (DWORD)lstrlenW(protocolDescription) + 1;
result = RegSetKeyValueW(key, NULL, L"URL Protocol", REG_SZ, &urlProtocol, sizeof(wchar_t));
if (FAILED(result))
fprintf(stderr, "Error writing description\n");
result = RegSetKeyValueW(
key, L"DefaultIcon", NULL, REG_SZ, exeFilePath, (exeLen + 1) * sizeof(wchar_t));
if (FAILED(result))
fprintf(stderr, "Error writing icon\n");
result = RegSetKeyValueW(
key, L"DefaultIcon", NULL, REG_SZ, exeFilePath, (exeLen + 1) * sizeof(wchar_t));
if (FAILED(result))
fprintf(stderr, "Error writing icon\n");
len = (DWORD)lstrlenW(openCommand) + 1;
result = RegSetKeyValueW(
key, L"shell\\open\\command", NULL, REG_SZ, openCommand, len * sizeof(wchar_t));
if (FAILED(result))
fprintf(stderr, "Error writing command\n");
RegCloseKey(key);
len = (DWORD)lstrlenW(openCommand) + 1;
result = RegSetKeyValueW(
key, L"shell\\open\\command", NULL, REG_SZ, openCommand, len * sizeof(wchar_t));
if (FAILED(result))
fprintf(stderr, "Error writing command\n");
RegCloseKey(key);
}
void Discord_Register(const char* applicationId, const char* command)
{
wchar_t appId[32];
wchar_t openCommand[1024];
const wchar_t* wcommand = NULL;
wchar_t openCommand[1024];
const wchar_t* wcommand = NULL;
wchar_t appId[32];
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
if (command && command[0])
{
const int commandBufferLen =
sizeof(openCommand) / sizeof(*openCommand);
MultiByteToWideChar(CP_UTF8, 0, command, -1,
openCommand, commandBufferLen);
wcommand = openCommand;
}
if (command && command[0])
{
const int commandBufferLen =
sizeof(openCommand) / sizeof(*openCommand);
MultiByteToWideChar(CP_UTF8, 0, command, -1,
openCommand, commandBufferLen);
wcommand = openCommand;
}
Discord_RegisterW(appId, wcommand);
Discord_RegisterW(appId, wcommand);
}
void Discord_RegisterSteamGame(
@ -171,8 +172,7 @@ void Discord_RegisterSteamGame(
status = RegQueryValueExW(key,
L"SteamExe", NULL, NULL, (BYTE*)steamPath, &pathBytes);
RegCloseKey(key);
if (status != ERROR_SUCCESS || pathBytes < 1)
{
if (status != ERROR_SUCCESS || pathBytes < 1) {
fprintf(stderr, "Error reading SteamExe key\n");
return;
}

View File

@ -1,7 +1,7 @@
#include <retro_common_api.h>
#include "discord_rpc.h"
#include "backoff.h"
#include "discord_register.h"
#include "msg_queue.h"
#include "rpc_connection.h"
#include "serialization.h"
@ -15,23 +15,14 @@
#include <thread>
#endif
/* Forward declarations */
#if defined(__cplusplus) && !defined(CXX_BUILD)
extern "C" {
#endif
void Discord_Register(const char *a, const char *b);
void Discord_RegisterSteamGame(const char *a, const char *b);
#if defined(__cplusplus) && !defined(CXX_BUILD)
}
#endif
constexpr size_t MaxMessageSize{16 * 1024};
constexpr size_t MessageQueueSize{8};
constexpr size_t JoinQueueSize{8};
struct QueuedMessage
{
size_t length;
char buffer[16384];
char buffer[MaxMessageSize];
void Copy(const QueuedMessage& other)
{
@ -59,32 +50,25 @@ struct User
* from future changes in these sizes */
};
static int Pid{0};
static int Nonce{1};
static int LastErrorCode{0};
static int LastDisconnectErrorCode{0};
static char JoinGameSecret[256];
static char SpectateGameSecret[256];
static char LastErrorMessage[256];
static char LastDisconnectErrorMessage[256];
static RpcConnection* Connection{nullptr};
static DiscordEventHandlers QueuedHandlers{};
static DiscordEventHandlers Handlers{};
static std::atomic_bool WasJustConnected{false};
static std::atomic_bool WasJustDisconnected{false};
static std::atomic_bool GotErrorMessage{false};
static std::atomic_bool WasJoinGame{false};
static std::atomic_bool WasSpectateGame{false};
static char JoinGameSecret[256];
static char SpectateGameSecret[256];
static int LastErrorCode{0};
static char LastErrorMessage[256];
static int LastDisconnectErrorCode{0};
static char LastDisconnectErrorMessage[256];
static std::mutex PresenceMutex;
static std::mutex HandlerMutex;
static QueuedMessage QueuedPresence{};
static MsgQueue<QueuedMessage, 8> SendQueue;
static MsgQueue<User, 8> JoinAskQueue;
static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
static MsgQueue<User, JoinQueueSize> JoinAskQueue;
static User connectedUser;
/* We want to auto connect, and retry on failure,
@ -92,57 +76,57 @@ static User connectedUser;
* backoff from 0.5 seconds to 1 minute */
static Backoff ReconnectTimeMs(500, 60 * 1000);
static auto NextConnect = std::chrono::system_clock::now();
static int Pid{0};
static int Nonce{1};
#ifndef DISCORD_DISABLE_IO_THREAD
static void Discord_UpdateConnection(void);
class IoThreadHolder
{
private:
std::atomic_bool keepRunning{true};
std::mutex waitForIOMutex;
std::condition_variable waitForIOActivity;
std::thread ioThread;
class IoThreadHolder {
private:
std::atomic_bool keepRunning{true};
std::mutex waitForIOMutex;
std::condition_variable waitForIOActivity;
std::thread ioThread;
public:
void Start()
{
keepRunning.store(true);
ioThread = std::thread([&]() {
const std::chrono::duration<int64_t, std::milli> maxWait{500LL};
Discord_UpdateConnection();
while (keepRunning.load()) {
std::unique_lock<std::mutex> lock(waitForIOMutex);
waitForIOActivity.wait_for(lock, maxWait);
Discord_UpdateConnection();
}
});
}
public:
void Start()
{
keepRunning.store(true);
ioThread = std::thread([&]() {
const std::chrono::duration<int64_t, std::milli> maxWait{500LL};
Discord_UpdateConnection();
while (keepRunning.load()) {
std::unique_lock<std::mutex> lock(waitForIOMutex);
waitForIOActivity.wait_for(lock, maxWait);
Discord_UpdateConnection();
}
});
}
void Notify() { waitForIOActivity.notify_all(); }
void Notify() { waitForIOActivity.notify_all(); }
void Stop()
{
keepRunning.exchange(false);
Notify();
if (ioThread.joinable())
void Stop()
{
keepRunning.exchange(false);
Notify();
if (ioThread.joinable())
ioThread.join();
}
}
~IoThreadHolder() { Stop(); }
~IoThreadHolder() { Stop(); }
};
#else
class IoThreadHolder
{
public:
void Start() {}
void Stop() {}
void Notify() {}
class IoThreadHolder {
public:
void Start() {}
void Stop() {}
void Notify() {}
};
#endif /* DISCORD_DISABLE_IO_THREAD */
static IoThreadHolder* IoThread{nullptr};
static void UpdateReconnectTime(void)
static void UpdateReconnectTime()
{
NextConnect = std::chrono::system_clock::now() +
std::chrono::duration<int64_t, std::milli>{ReconnectTimeMs.nextDelay()};
@ -176,18 +160,18 @@ static void Discord_UpdateConnection(void)
if (!Connection->Read(message))
break;
const char *evtName = GetStrMember(&message, "evt");
const char *nonce = GetStrMember(&message, "nonce");
const char* evtName = GetStrMember(&message, "evt");
const char* nonce = GetStrMember(&message, "nonce");
if (nonce)
{
/* in responses only --
* should use to match up response when needed. */
if (evtName && !strcmp(evtName, "ERROR"))
if (evtName && strcmp(evtName, "ERROR") == 0)
{
JsonValue *data = GetObjMember(&message, "data");
LastErrorCode = GetIntMember(data, "code");
auto data = GetObjMember(&message, "data");
LastErrorCode = GetIntMember(data, "code");
StringCopy(LastErrorMessage, GetStrMember(data, "message", ""));
GotErrorMessage.store(true);
}
@ -198,40 +182,39 @@ static void Discord_UpdateConnection(void)
if (!evtName)
continue;
JsonValue *data = GetObjMember(&message, "data");
auto data = GetObjMember(&message, "data");
if (!strcmp(evtName, "ACTIVITY_JOIN"))
if (strcmp(evtName, "ACTIVITY_JOIN") == 0)
{
const char *secret = GetStrMember(data, "secret");
auto secret = GetStrMember(data, "secret");
if (secret)
{
StringCopy(JoinGameSecret, secret);
WasJoinGame.store(true);
}
}
else if (!strcmp(evtName, "ACTIVITY_SPECTATE"))
else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0)
{
const char *secret = GetStrMember(data, "secret");
auto secret = GetStrMember(data, "secret");
if (secret)
{
StringCopy(SpectateGameSecret, secret);
WasSpectateGame.store(true);
}
}
else if (!strcmp(evtName, "ACTIVITY_JOIN_REQUEST"))
else if (strcmp(evtName, "ACTIVITY_JOIN_REQUEST") == 0)
{
JsonValue *user = GetObjMember(data, "user");
const char *userId = GetStrMember(user, "id");
const char *username = GetStrMember(user, "username");
const char *avatar = GetStrMember(user, "avatar");
auto joinReq = JoinAskQueue.GetNextAddMessage();
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);
const char *discriminator = GetStrMember(user,
"discriminator");
auto discriminator = GetStrMember(user, "discriminator");
if (discriminator)
StringCopy(joinReq->discriminator, discriminator);
if (avatar)
@ -338,16 +321,16 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(
Connection->onConnect = [](JsonDocument& readyMessage)
{
Discord_UpdateHandlers(&QueuedHandlers);
JsonValue *data = GetObjMember(&readyMessage, "data");
JsonValue *user = GetObjMember(data, "user");
const char *userId = GetStrMember(user, "id");
const char *username = GetStrMember(user, "username");
const char *avatar = GetStrMember(user, "avatar");
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);
const char *discriminator = GetStrMember(user, "discriminator");
auto discriminator = GetStrMember(user, "discriminator");
if (discriminator)
StringCopy(connectedUser.discriminator, discriminator);
if (avatar)

View File

@ -36,8 +36,8 @@ void RpcConnection::Open()
JsonDocument message;
if (Read(message))
{
const char *cmd = GetStrMember(&message, "cmd");
const char *evt = GetStrMember(&message, "evt");
auto cmd = GetStrMember(&message, "cmd");
auto evt = GetStrMember(&message, "evt");
if (cmd && evt
&& !strcmp(cmd, "DISPATCH")
&& !strcmp(evt, "READY"))

View File

@ -5,7 +5,7 @@
/* I took this from the buffer size libuv uses for named pipes;
* I suspect ours would usually be much smaller. */
#define MAX_RPC_FRAMESIZE 65536
constexpr size_t MaxRpcFrameSize = 64 * 1024;
struct RpcConnection
{
@ -33,7 +33,7 @@ struct RpcConnection
struct MessageFrame : public MessageFrameHeader
{
char message[MAX_RPC_FRAMESIZE - sizeof(MessageFrameHeader)];
char message[MaxRpcFrameSize - sizeof(MessageFrameHeader)];
};
enum class State : uint32_t