(Discord) Convert some files to C

This commit is contained in:
twinaphex 2019-08-20 21:03:16 +02:00
parent 12698f561f
commit a363af7796
10 changed files with 517 additions and 506 deletions

View File

@ -0,0 +1,98 @@
#include "discord_rpc.h"
#include "discord_register.h"
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
static bool Mkdir(const char* path)
{
int result = mkdir(path, 0755);
if (result == 0)
return true;
if (errno == EEXIST)
return true;
return false;
}
/* 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)
{
FILE* fp;
int fileLen;
char xdgMimeCommand[1024];
char desktopFilename[256];
char desktopFilePath[1024];
char desktopFile[2048];
/* Add a desktop file and update some MIME handlers
* so that xdg-open does the right thing. */
char exePath[1024];
const char* home = getenv("HOME");
if (!home)
return;
if (!command || !command[0])
{
ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath));
if (size <= 0 || size >= (ssize_t)sizeof(exePath))
return;
exePath[size] = '\0';
command = exePath;
}
{
const char* desktopFileFormat = "[Desktop Entry]\n"
"Name=Game %s\n"
"Exec=%s\n" /* note: it really wants that %u in there */
"Type=Application\n"
"NoDisplay=true\n"
"Categories=Discord;Games;\n"
"MimeType=x-scheme-handler/discord-%s;\n";
fileLen = snprintf(
desktopFile, sizeof(desktopFile), desktopFileFormat, applicationId, command, applicationId);
if (fileLen <= 0)
return;
}
snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId);
snprintf(desktopFilePath, sizeof(desktopFilePath), "%s/.local", home);
if (!Mkdir(desktopFilePath))
return;
strcat(desktopFilePath, "/share");
if (!Mkdir(desktopFilePath))
return;
strcat(desktopFilePath, "/applications");
if (!Mkdir(desktopFilePath))
return;
strcat(desktopFilePath, desktopFilename);
fp = fopen(desktopFilePath, "w");
if (!fp)
return;
fwrite(desktopFile, 1, fileLen, fp);
fclose(fp);
snprintf(xdgMimeCommand,
sizeof(xdgMimeCommand),
"xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s",
applicationId,
applicationId);
if (system(xdgMimeCommand) < 0)
fprintf(stderr, "Failed to register mime handler\n");
}
void Discord_RegisterSteamGame(
const char* applicationId,
const char* steamId)
{
char command[256];
sprintf(command, "xdg-open steam://rungameid/%s", steamId);
Discord_Register(applicationId, command);
}

View File

@ -1,102 +0,0 @@
#include "discord_rpc.h"
#include "discord_register.h"
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
static bool Mkdir(const char* path)
{
int result = mkdir(path, 0755);
if (result == 0) {
return true;
}
if (errno == EEXIST) {
return true;
}
return false;
}
// we want to register games so we can run them from Discord client as discord-<appid>://
extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command)
{
// Add a desktop file and update some mime handlers so that xdg-open does the right thing.
const char* home = getenv("HOME");
if (!home) {
return;
}
char exePath[1024];
if (!command || !command[0]) {
ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath));
if (size <= 0 || size >= (ssize_t)sizeof(exePath)) {
return;
}
exePath[size] = '\0';
command = exePath;
}
const char* destopFileFormat = "[Desktop Entry]\n"
"Name=Game %s\n"
"Exec=%s\n" // note: it really wants that %u in there
"Type=Application\n"
"NoDisplay=true\n"
"Categories=Discord;Games;\n"
"MimeType=x-scheme-handler/discord-%s;\n";
char desktopFile[2048];
int fileLen = snprintf(
desktopFile, sizeof(desktopFile), destopFileFormat, applicationId, command, applicationId);
if (fileLen <= 0) {
return;
}
char desktopFilename[256];
snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId);
char desktopFilePath[1024];
snprintf(desktopFilePath, sizeof(desktopFilePath), "%s/.local", home);
if (!Mkdir(desktopFilePath)) {
return;
}
strcat(desktopFilePath, "/share");
if (!Mkdir(desktopFilePath)) {
return;
}
strcat(desktopFilePath, "/applications");
if (!Mkdir(desktopFilePath)) {
return;
}
strcat(desktopFilePath, desktopFilename);
FILE* fp = fopen(desktopFilePath, "w");
if (fp) {
fwrite(desktopFile, 1, fileLen, fp);
fclose(fp);
}
else {
return;
}
char xdgMimeCommand[1024];
snprintf(xdgMimeCommand,
sizeof(xdgMimeCommand),
"xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s",
applicationId,
applicationId);
if (system(xdgMimeCommand) < 0) {
fprintf(stderr, "Failed to register mime handler\n");
}
}
extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId,
const char* steamId)
{
char command[256];
sprintf(command, "xdg-open steam://rungameid/%s", steamId);
Discord_Register(applicationId, command);
}

View File

@ -0,0 +1,191 @@
#include "discord_rpc.h"
#include "discord_register.h"
#define WIN32_LEAN_AND_MEAN
#define NOMCX
#define NOSERVICE
#define NOIME
#include <windows.h>
#include <psapi.h>
#include <wchar.h>
#include <stdio.h>
/**
* Updated fixes for MinGW and WinXP
* This block is written the way it does not involve changing the rest of the code
* Checked to be compiling
* 1) strsafe.h belongs to Windows SDK and cannot be added to MinGW
* #include guarded, functions redirected to <string.h> substitutes
* 2) RegSetKeyValueW and LSTATUS are not declared in <winreg.h>
* The entire function is rewritten
*/
#ifdef __MINGW32__
/* strsafe.h fixes */
static HRESULT StringCbPrintfW(LPWSTR pszDest,
size_t cbDest, LPCWSTR pszFormat, ...)
{
HRESULT ret;
va_list va;
va_start(va, pszFormat);
cbDest /= 2; /* Size is divided by 2 to convert from bytes to wide characters - causes segfault */
/* othervise */
ret = vsnwprintf(pszDest, cbDest, pszFormat, va);
pszDest[cbDest - 1] = 0; /* Terminate the string in case a buffer overflow; -1 will be returned */
va_end(va);
return ret;
}
#else
#include <strsafe.h>
#endif /* __MINGW32__ */
/* winreg.h fixes */
#ifndef LSTATUS
#define LSTATUS LONG
#endif
#ifdef RegSetKeyValueW
#undefine RegSetKeyValueW
#endif
#define RegSetKeyValueW regset
static LSTATUS regset(HKEY hkey,
LPCWSTR subkey,
LPCWSTR name,
DWORD type,
const void* data,
DWORD len)
{
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;
}
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. */
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);
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 =
RegSetKeyValueW(key, NULL, NULL, REG_SZ, protocolDescription, len * 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");
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 openCommand[1024];
const wchar_t* wcommand = NULL;
wchar_t 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;
}
Discord_RegisterW(appId, wcommand);
}
void Discord_RegisterSteamGame(
const char* applicationId,
const char* steamId)
{
DWORD pathChars, pathBytes, i;
HKEY key;
wchar_t steamPath[MAX_PATH];
wchar_t command[1024];
wchar_t appId[32];
wchar_t wSteamId[32];
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
MultiByteToWideChar(CP_UTF8, 0, steamId, -1, wSteamId, 32);
LSTATUS status = RegOpenKeyExW(HKEY_CURRENT_USER,
L"Software\\Valve\\Steam", 0, KEY_READ, &key);
if (status != ERROR_SUCCESS)
{
fprintf(stderr, "Error opening Steam key\n");
return;
}
pathBytes = sizeof(steamPath);
status = RegQueryValueExW(key,
L"SteamExe", NULL, NULL, (BYTE*)steamPath, &pathBytes);
RegCloseKey(key);
if (status != ERROR_SUCCESS || pathBytes < 1) {
fprintf(stderr, "Error reading SteamExe key\n");
return;
}
pathChars = pathBytes / sizeof(wchar_t);
for (i = 0; i < pathChars; ++i)
{
if (steamPath[i] == L'/')
steamPath[i] = L'\\';
}
StringCbPrintfW(command, sizeof(command),
L"\"%s\" steam://rungameid/%s", steamPath, wSteamId);
Discord_RegisterW(appId, command);
}

View File

@ -1,185 +0,0 @@
#include "discord_rpc.h"
#include "discord_register.h"
#define WIN32_LEAN_AND_MEAN
#define NOMCX
#define NOSERVICE
#define NOIME
#include <windows.h>
#include <psapi.h>
#include <cwchar>
#include <cstdio>
/**
* Updated fixes for MinGW and WinXP
* This block is written the way it does not involve changing the rest of the code
* Checked to be compiling
* 1) strsafe.h belongs to Windows SDK and cannot be added to MinGW
* #include guarded, functions redirected to <string.h> substitutes
* 2) RegSetKeyValueW and LSTATUS are not declared in <winreg.h>
* The entire function is rewritten
*/
#ifdef __MINGW32__
/// strsafe.h fixes
static HRESULT StringCbPrintfW(LPWSTR pszDest, size_t cbDest, LPCWSTR pszFormat, ...)
{
HRESULT ret;
va_list va;
va_start(va, pszFormat);
cbDest /= 2; // Size is divided by 2 to convert from bytes to wide characters - causes segfault
// othervise
ret = vsnwprintf(pszDest, cbDest, pszFormat, va);
pszDest[cbDest - 1] = 0; // Terminate the string in case a buffer overflow; -1 will be returned
va_end(va);
return ret;
}
#else
#include <strsafe.h>
#endif // __MINGW32__
/// winreg.h fixes
#ifndef LSTATUS
#define LSTATUS LONG
#endif
#ifdef RegSetKeyValueW
#undefine RegSetKeyValueW
#endif
#define RegSetKeyValueW regset
static LSTATUS regset(HKEY hkey,
LPCWSTR subkey,
LPCWSTR name,
DWORD type,
const void* data,
DWORD len)
{
HKEY htkey = hkey, hsubkey = nullptr;
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;
}
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.
wchar_t exeFilePath[MAX_PATH];
DWORD exeLen = GetModuleFileNameW(nullptr, exeFilePath, MAX_PATH);
wchar_t openCommand[1024];
if (command && command[0]) {
StringCbPrintfW(openCommand, sizeof(openCommand), L"%S", command);
}
else {
// StringCbCopyW(openCommand, sizeof(openCommand), exeFilePath);
StringCbPrintfW(openCommand, sizeof(openCommand), L"%S", exeFilePath);
}
wchar_t protocolName[64];
StringCbPrintfW(protocolName, sizeof(protocolName), L"discord-%S", applicationId);
wchar_t protocolDescription[128];
StringCbPrintfW(
protocolDescription, sizeof(protocolDescription), L"URL:Run game %S protocol", applicationId);
wchar_t urlProtocol = 0;
wchar_t keyName[256];
StringCbPrintfW(keyName, sizeof(keyName), L"Software\\Classes\\%S", protocolName);
HKEY key;
auto status =
RegCreateKeyExW(HKEY_CURRENT_USER, keyName, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr);
if (status != ERROR_SUCCESS) {
fprintf(stderr, "Error creating key\n");
return;
}
DWORD len;
LSTATUS result;
len = (DWORD)lstrlenW(protocolDescription) + 1;
result =
RegSetKeyValueW(key, nullptr, nullptr, REG_SZ, protocolDescription, len * sizeof(wchar_t));
if (FAILED(result)) {
fprintf(stderr, "Error writing description\n");
}
len = (DWORD)lstrlenW(protocolDescription) + 1;
result = RegSetKeyValueW(key, nullptr, L"URL Protocol", REG_SZ, &urlProtocol, sizeof(wchar_t));
if (FAILED(result)) {
fprintf(stderr, "Error writing description\n");
}
result = RegSetKeyValueW(
key, L"DefaultIcon", nullptr, 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", nullptr, REG_SZ, openCommand, len * sizeof(wchar_t));
if (FAILED(result)) {
fprintf(stderr, "Error writing command\n");
}
RegCloseKey(key);
}
extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command)
{
wchar_t appId[32];
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
wchar_t openCommand[1024];
const wchar_t* wcommand = nullptr;
if (command && command[0]) {
const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand);
MultiByteToWideChar(CP_UTF8, 0, command, -1, openCommand, commandBufferLen);
wcommand = openCommand;
}
Discord_RegisterW(appId, wcommand);
}
extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId,
const char* steamId)
{
wchar_t appId[32];
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
wchar_t wSteamId[32];
MultiByteToWideChar(CP_UTF8, 0, steamId, -1, wSteamId, 32);
HKEY key;
auto status = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam", 0, KEY_READ, &key);
if (status != ERROR_SUCCESS) {
fprintf(stderr, "Error opening Steam key\n");
return;
}
wchar_t steamPath[MAX_PATH];
DWORD pathBytes = sizeof(steamPath);
status = RegQueryValueExW(key, L"SteamExe", nullptr, nullptr, (BYTE*)steamPath, &pathBytes);
RegCloseKey(key);
if (status != ERROR_SUCCESS || pathBytes < 1) {
fprintf(stderr, "Error reading SteamExe key\n");
return;
}
DWORD pathChars = pathBytes / sizeof(wchar_t);
for (DWORD i = 0; i < pathChars; ++i) {
if (steamPath[i] == L'/') {
steamPath[i] = L'\\';
}
}
wchar_t command[1024];
StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://rungameid/%s", steamPath, wSteamId);
Discord_RegisterW(appId, command);
}

View File

@ -19,20 +19,21 @@ constexpr size_t MaxMessageSize{16 * 1024};
constexpr size_t MessageQueueSize{8};
constexpr size_t JoinQueueSize{8};
struct QueuedMessage {
size_t length;
char buffer[MaxMessageSize];
struct QueuedMessage
{
size_t length;
char buffer[MaxMessageSize];
void Copy(const QueuedMessage& other)
{
length = other.length;
if (length) {
memcpy(buffer, other.buffer, length);
}
}
void Copy(const QueuedMessage& other)
{
length = other.length;
if (length)
memcpy(buffer, other.buffer, length);
}
};
struct User {
struct User
{
// snowflake (64bit int), turned into a ascii decimal string, at most 20 chars +1 null
// terminator = 21
char userId[32];
@ -104,9 +105,8 @@ public:
{
keepRunning.exchange(false);
Notify();
if (ioThread.joinable()) {
if (ioThread.joinable())
ioThread.join();
}
}
~IoThreadHolder() { Stop(); }
@ -123,7 +123,7 @@ static IoThreadHolder* IoThread{nullptr};
static void UpdateReconnectTime()
{
NextConnect = std::chrono::system_clock::now() +
NextConnect = std::chrono::system_clock::now() +
std::chrono::duration<int64_t, std::milli>{ReconnectTimeMs.nextDelay()};
}
@ -133,102 +133,111 @@ extern "C" DISCORD_EXPORT void Discord_UpdateConnection(void)
static void Discord_UpdateConnection(void)
#endif
{
if (!Connection) {
if (!Connection)
return;
}
if (!Connection->IsOpen()) {
if (std::chrono::system_clock::now() >= NextConnect) {
if (!Connection->IsOpen())
{
if (std::chrono::system_clock::now() >= NextConnect)
{
UpdateReconnectTime();
Connection->Open();
}
}
else {
else
{
// reads
for (;;) {
for (;;)
{
JsonDocument message;
if (!Connection->Read(message)) {
if (!Connection->Read(message))
break;
}
const char* evtName = GetStrMember(&message, "evt");
const char* nonce = GetStrMember(&message, "nonce");
if (nonce) {
if (nonce)
{
// in responses only -- should use to match up response when needed.
if (evtName && strcmp(evtName, "ERROR") == 0) {
if (evtName && strcmp(evtName, "ERROR") == 0)
{
auto data = GetObjMember(&message, "data");
LastErrorCode = GetIntMember(data, "code");
StringCopy(LastErrorMessage, GetStrMember(data, "message", ""));
GotErrorMessage.store(true);
}
}
else {
else
{
// should have evt == name of event, optional data
if (evtName == nullptr) {
if (evtName == nullptr)
continue;
}
auto data = GetObjMember(&message, "data");
if (strcmp(evtName, "ACTIVITY_JOIN") == 0) {
if (strcmp(evtName, "ACTIVITY_JOIN") == 0)
{
auto secret = GetStrMember(data, "secret");
if (secret) {
if (secret)
{
StringCopy(JoinGameSecret, secret);
WasJoinGame.store(true);
}
}
else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0) {
auto secret = GetStrMember(data, "secret");
if (secret) {
StringCopy(SpectateGameSecret, secret);
WasSpectateGame.store(true);
}
else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0)
{
auto secret = GetStrMember(data, "secret");
if (secret)
{
StringCopy(SpectateGameSecret, secret);
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);
}
if (avatar) {
StringCopy(joinReq->avatar, avatar);
}
else {
joinReq->avatar[0] = 0;
}
JoinAskQueue.CommitAdd();
}
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);
if (avatar)
StringCopy(joinReq->avatar, avatar);
else
joinReq->avatar[0] = 0;
JoinAskQueue.CommitAdd();
}
}
}
}
// writes
if (QueuedPresence.length) {
QueuedMessage local;
{
std::lock_guard<std::mutex> guard(PresenceMutex);
local.Copy(QueuedPresence);
QueuedPresence.length = 0;
}
if (!Connection->Write(local.buffer, local.length)) {
// if we fail to send, requeue
std::lock_guard<std::mutex> guard(PresenceMutex);
QueuedPresence.Copy(local);
}
if (QueuedPresence.length)
{
QueuedMessage local;
{
std::lock_guard<std::mutex> guard(PresenceMutex);
local.Copy(QueuedPresence);
QueuedPresence.length = 0;
}
if (!Connection->Write(local.buffer, local.length)) {
// if we fail to send, requeue
std::lock_guard<std::mutex> guard(PresenceMutex);
QueuedPresence.Copy(local);
}
}
while (SendQueue.HavePendingSends()) {
while (SendQueue.HavePendingSends())
{
auto qmessage = SendQueue.GetNextSendMessage();
Connection->Write(qmessage->buffer, qmessage->length);
SendQueue.CommitSend();
@ -236,13 +245,6 @@ static void Discord_UpdateConnection(void)
}
}
static void SignalIOActivity()
{
if (IoThread != nullptr) {
IoThread->Notify();
}
}
static bool RegisterForEvent(const char* evtName)
{
auto qmessage = SendQueue.GetNextAddMessage();
@ -250,7 +252,8 @@ static bool RegisterForEvent(const char* evtName)
qmessage->length =
JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
SendQueue.CommitAdd();
SignalIOActivity();
if (IoThread)
IoThread->Notify();
return true;
}
return false;
@ -263,7 +266,8 @@ static bool DeregisterForEvent(const char* evtName)
qmessage->length =
JsonWriteUnsubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
SendQueue.CommitAdd();
SignalIOActivity();
if (IoThread)
IoThread->Notify();
return true;
}
return false;
@ -308,31 +312,31 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
}
Connection = RpcConnection::Create(applicationId);
Connection->onConnect = [](JsonDocument& readyMessage) {
Connection->onConnect = [](JsonDocument& readyMessage)
{
Discord_UpdateHandlers(&QueuedHandlers);
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) {
if (userId && username)
{
StringCopy(connectedUser.userId, userId);
StringCopy(connectedUser.username, username);
auto discriminator = GetStrMember(user, "discriminator");
if (discriminator) {
if (discriminator)
StringCopy(connectedUser.discriminator, discriminator);
}
if (avatar) {
if (avatar)
StringCopy(connectedUser.avatar, avatar);
}
else {
else
connectedUser.avatar[0] = 0;
}
}
WasJustConnected.exchange(true);
ReconnectTimeMs.reset();
};
Connection->onDisconnect = [](int err, const char* message) {
Connection->onDisconnect = [](int err, const char* message)
{
LastDisconnectErrorCode = err;
StringCopy(LastDisconnectErrorMessage, message);
{
@ -348,13 +352,13 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
extern "C" DISCORD_EXPORT void Discord_Shutdown(void)
{
if (!Connection) {
if (!Connection)
return;
}
Connection->onConnect = nullptr;
Connection->onConnect = nullptr;
Connection->onDisconnect = nullptr;
Handlers = {};
if (IoThread != nullptr) {
if (IoThread != nullptr)
{
IoThread->Stop();
delete IoThread;
IoThread = nullptr;
@ -370,7 +374,8 @@ extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence*
QueuedPresence.length = JsonWriteRichPresenceObj(
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
}
SignalIOActivity();
if (IoThread)
IoThread->Notify();
}
extern "C" DISCORD_EXPORT void Discord_ClearPresence(void)
@ -381,15 +386,16 @@ extern "C" DISCORD_EXPORT void Discord_ClearPresence(void)
extern "C" DISCORD_EXPORT void Discord_Respond(const char* userId, /* DISCORD_REPLY_ */ int reply)
{
// if we are not connected, let's not batch up stale messages for later
if (!Connection || !Connection->IsOpen()) {
if (!Connection || !Connection->IsOpen())
return;
}
auto qmessage = SendQueue.GetNextAddMessage();
if (qmessage) {
if (qmessage)
{
qmessage->length =
JsonWriteJoinReply(qmessage->buffer, sizeof(qmessage->buffer), userId, reply, Nonce++);
SendQueue.CommitAdd();
SignalIOActivity();
if (IoThread)
IoThread->Notify();
}
}
@ -399,24 +405,25 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
// of times inbetween calls here. Externally, we want the sequence to seem sane, so any other
// signals are book-ended by calls to ready and disconnect.
if (!Connection) {
if (!Connection)
return;
}
bool wasDisconnected = WasJustDisconnected.exchange(false);
bool isConnected = Connection->IsOpen();
bool isConnected = Connection->IsOpen();
if (isConnected) {
if (isConnected)
{
// if we are connected, disconnect cb first
std::lock_guard<std::mutex> guard(HandlerMutex);
if (wasDisconnected && Handlers.disconnected) {
if (wasDisconnected && Handlers.disconnected)
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
}
}
if (WasJustConnected.exchange(false)) {
if (WasJustConnected.exchange(false))
{
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.ready) {
if (Handlers.ready)
{
DiscordUser du{connectedUser.userId,
connectedUser.username,
connectedUser.discriminator,
@ -425,25 +432,25 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
}
}
if (GotErrorMessage.exchange(false)) {
if (GotErrorMessage.exchange(false))
{
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.errored) {
if (Handlers.errored)
Handlers.errored(LastErrorCode, LastErrorMessage);
}
}
if (WasJoinGame.exchange(false)) {
if (WasJoinGame.exchange(false))
{
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.joinGame) {
if (Handlers.joinGame)
Handlers.joinGame(JoinGameSecret);
}
}
if (WasSpectateGame.exchange(false)) {
if (WasSpectateGame.exchange(false))
{
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.spectateGame) {
if (Handlers.spectateGame)
Handlers.spectateGame(SpectateGameSecret);
}
}
// Right now this batches up any requests and sends them all in a burst; I could imagine a world
@ -451,11 +458,13 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
// is sent. I left it this way because I could also imagine wanting to process these all and
// maybe show them in one common dialog and/or start fetching the avatars in parallel, and if
// not it should be trivial for the implementer to make a queue themselves.
while (JoinAskQueue.HavePendingSends()) {
while (JoinAskQueue.HavePendingSends())
{
auto req = JoinAskQueue.GetNextSendMessage();
{
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.joinRequest) {
if (Handlers.joinRequest)
{
DiscordUser du{req->userId, req->username, req->discriminator, req->avatar};
Handlers.joinRequest(&du);
}
@ -463,38 +472,37 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
JoinAskQueue.CommitSend();
}
if (!isConnected) {
if (!isConnected)
{
// if we are not connected, disconnect message last
std::lock_guard<std::mutex> guard(HandlerMutex);
if (wasDisconnected && Handlers.disconnected) {
if (wasDisconnected && Handlers.disconnected)
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
}
}
}
#ifndef HANDLE_EVENT_REGISTRATION
#define HANDLE_EVENT_REGISTRATION(handler_name, event) \
if (!Handlers.handler_name && newHandlers->handler_name) \
RegisterForEvent(event); \
else if (Handlers.handler_name && !newHandlers->handler_name) \
DeregisterForEvent(event)
#endif
extern "C" DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* newHandlers)
{
if (newHandlers) {
#define HANDLE_EVENT_REGISTRATION(handler_name, event) \
if (!Handlers.handler_name && newHandlers->handler_name) { \
RegisterForEvent(event); \
} \
else if (Handlers.handler_name && !newHandlers->handler_name) { \
DeregisterForEvent(event); \
}
if (newHandlers)
{
std::lock_guard<std::mutex> guard(HandlerMutex);
HANDLE_EVENT_REGISTRATION(joinGame, "ACTIVITY_JOIN");
HANDLE_EVENT_REGISTRATION(spectateGame, "ACTIVITY_SPECTATE");
HANDLE_EVENT_REGISTRATION(joinRequest, "ACTIVITY_JOIN_REQUEST");
std::lock_guard<std::mutex> guard(HandlerMutex);
HANDLE_EVENT_REGISTRATION(joinGame, "ACTIVITY_JOIN")
HANDLE_EVENT_REGISTRATION(spectateGame, "ACTIVITY_SPECTATE")
HANDLE_EVENT_REGISTRATION(joinRequest, "ACTIVITY_JOIN_REQUEST")
#undef HANDLE_EVENT_REGISTRATION
Handlers = *newHandlers;
}
else {
std::lock_guard<std::mutex> guard(HandlerMutex);
Handlers = {};
}
return;
Handlers = *newHandlers;
}
else
{
std::lock_guard<std::mutex> guard(HandlerMutex);
Handlers = {};
}
}

View File

@ -1,8 +0,0 @@
#include <windows.h>
// outsmart GCC's missing-declarations warning
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID);
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID)
{
return TRUE;
}

View File

@ -2,8 +2,8 @@
#include <atomic>
// A simple queue. No locks, but only works with a single thread as producer and a single thread as
// a consumer. Mutex up as needed.
/* A simple queue. No locks, but only works with a single thread as producer and a single thread as
* a consumer. Mutex up as needed. */
template <typename ElementType, size_t QueueSize>
class MsgQueue {
@ -17,10 +17,9 @@ public:
ElementType* GetNextAddMessage()
{
// if we are falling behind, bail
if (pendingSends_.load() >= QueueSize) {
/* if we are falling behind, bail */
if (pendingSends_.load() >= QueueSize)
return nullptr;
}
auto index = (nextAdd_++) % QueueSize;
return &queue_[index];
}

View File

@ -22,50 +22,49 @@ static RpcConnection Instance;
void RpcConnection::Open()
{
if (state == State::Connected) {
if (state == State::Connected)
return;
if (state == State::Disconnected)
{
if (!connection->Open())
return;
}
if (state == State::Disconnected) {
if (connection->Open()) {
}
else {
return;
}
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"))
{
state = State::Connected;
if (onConnect)
onConnect(message);
}
}
}
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")) {
state = State::Connected;
if (onConnect) {
onConnect(message);
}
}
}
}
else {
else
{
sendFrame.opcode = Opcode::Handshake;
sendFrame.length = (uint32_t)JsonWriteHandshakeObj(
sendFrame.message, sizeof(sendFrame.message), RpcVersion, appId);
if (connection->Write(&sendFrame, sizeof(MessageFrameHeader) + sendFrame.length)) {
if (connection->Write(&sendFrame, sizeof(MessageFrameHeader) + sendFrame.length))
state = State::SentHandshake;
}
else {
else
Close();
}
}
}
void RpcConnection::Close()
{
if (onDisconnect && (state == State::Connected || state == State::SentHandshake)) {
if (onDisconnect && (state == State::Connected || state == State::SentHandshake))
onDisconnect(lastErrorCode, lastErrorMessage);
}
connection->Close();
state = State::Disconnected;
}
@ -75,7 +74,8 @@ bool RpcConnection::Write(const void* data, size_t length)
sendFrame.opcode = Opcode::Frame;
memcpy(sendFrame.message, data, length);
sendFrame.length = (uint32_t)length;
if (!connection->Write(&sendFrame, sizeof(MessageFrameHeader) + length)) {
if (!connection->Write(&sendFrame, sizeof(MessageFrameHeader) + length))
{
Close();
return false;
}
@ -84,14 +84,16 @@ bool RpcConnection::Write(const void* data, size_t length)
bool RpcConnection::Read(JsonDocument& message)
{
if (state != State::Connected && state != State::SentHandshake) {
if (state != State::Connected && state != State::SentHandshake)
return false;
}
MessageFrame readFrame;
for (;;) {
for (;;)
{
bool didRead = connection->Read(&readFrame, sizeof(MessageFrameHeader));
if (!didRead) {
if (!connection->isOpen) {
if (!didRead)
{
if (!connection->isOpen)
{
lastErrorCode = (int)ErrorCode::PipeClosed;
StringCopy(lastErrorMessage, "Pipe closed");
Close();
@ -99,9 +101,11 @@ bool RpcConnection::Read(JsonDocument& message)
return false;
}
if (readFrame.length > 0) {
if (readFrame.length > 0)
{
didRead = connection->Read(readFrame.message, readFrame.length);
if (!didRead) {
if (!didRead)
{
lastErrorCode = (int)ErrorCode::ReadCorrupt;
StringCopy(lastErrorMessage, "Partial data in frame");
Close();
@ -110,32 +114,33 @@ bool RpcConnection::Read(JsonDocument& message)
readFrame.message[readFrame.length] = 0;
}
switch (readFrame.opcode) {
case Opcode::Close: {
message.ParseInsitu(readFrame.message);
lastErrorCode = GetIntMember(&message, "code");
StringCopy(lastErrorMessage, GetStrMember(&message, "message", ""));
Close();
return false;
}
case Opcode::Frame:
message.ParseInsitu(readFrame.message);
return true;
case Opcode::Ping:
readFrame.opcode = Opcode::Pong;
if (!connection->Write(&readFrame, sizeof(MessageFrameHeader) + readFrame.length)) {
Close();
}
break;
case Opcode::Pong:
break;
case Opcode::Handshake:
default:
// something bad happened
lastErrorCode = (int)ErrorCode::ReadCorrupt;
StringCopy(lastErrorMessage, "Bad ipc frame");
Close();
return false;
switch (readFrame.opcode)
{
case Opcode::Close:
{
message.ParseInsitu(readFrame.message);
lastErrorCode = GetIntMember(&message, "code");
StringCopy(lastErrorMessage, GetStrMember(&message, "message", ""));
Close();
return false;
}
case Opcode::Frame:
message.ParseInsitu(readFrame.message);
return true;
case Opcode::Ping:
readFrame.opcode = Opcode::Pong;
if (!connection->Write(&readFrame, sizeof(MessageFrameHeader) + readFrame.length))
Close();
break;
case Opcode::Pong:
break;
case Opcode::Handshake:
default:
// something bad happened
lastErrorCode = (int)ErrorCode::ReadCorrupt;
StringCopy(lastErrorMessage, "Bad ipc frame");
Close();
return false;
}
}
}

View File

@ -1526,6 +1526,15 @@ HTTP SERVER
============================================================ */
#if defined(HAVE_DISCORD)
#include "../discord/discord.c"
#if defined(_WIN32)
#include "../deps/discord-rpc/src/discord_register_win.c"
#endif
#if defined(__linux__)
#include "../deps/discord-rpc/src/discord_register_linux.c"
#endif
#endif
/*============================================================

View File

@ -144,12 +144,8 @@ FONTS
#include "../deps/discord-rpc/src/serialization.cpp"
#if defined(_WIN32)
#include "../deps/discord-rpc/src/discord_register_win.cpp"
#include "../deps/discord-rpc/src/connection_win.cpp"
#endif
#if defined(__linux__)
#include "../deps/discord-rpc/src/discord_register_linux.cpp"
#endif
#if defined(__unix__) || defined(__APPLE__)
#include "../deps/discord-rpc/src/connection_unix.cpp"
#endif