(Discord) Convert some files to C
This commit is contained in:
parent
12698f561f
commit
a363af7796
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -19,20 +19,21 @@ constexpr size_t MaxMessageSize{16 * 1024};
|
||||||
constexpr size_t MessageQueueSize{8};
|
constexpr size_t MessageQueueSize{8};
|
||||||
constexpr size_t JoinQueueSize{8};
|
constexpr size_t JoinQueueSize{8};
|
||||||
|
|
||||||
struct QueuedMessage {
|
struct QueuedMessage
|
||||||
size_t length;
|
{
|
||||||
char buffer[MaxMessageSize];
|
size_t length;
|
||||||
|
char buffer[MaxMessageSize];
|
||||||
|
|
||||||
void Copy(const QueuedMessage& other)
|
void Copy(const QueuedMessage& other)
|
||||||
{
|
{
|
||||||
length = other.length;
|
length = other.length;
|
||||||
if (length) {
|
if (length)
|
||||||
memcpy(buffer, other.buffer, 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
|
// snowflake (64bit int), turned into a ascii decimal string, at most 20 chars +1 null
|
||||||
// terminator = 21
|
// terminator = 21
|
||||||
char userId[32];
|
char userId[32];
|
||||||
|
@ -104,9 +105,8 @@ public:
|
||||||
{
|
{
|
||||||
keepRunning.exchange(false);
|
keepRunning.exchange(false);
|
||||||
Notify();
|
Notify();
|
||||||
if (ioThread.joinable()) {
|
if (ioThread.joinable())
|
||||||
ioThread.join();
|
ioThread.join();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~IoThreadHolder() { Stop(); }
|
~IoThreadHolder() { Stop(); }
|
||||||
|
@ -123,7 +123,7 @@ static IoThreadHolder* IoThread{nullptr};
|
||||||
|
|
||||||
static void UpdateReconnectTime()
|
static void UpdateReconnectTime()
|
||||||
{
|
{
|
||||||
NextConnect = std::chrono::system_clock::now() +
|
NextConnect = std::chrono::system_clock::now() +
|
||||||
std::chrono::duration<int64_t, std::milli>{ReconnectTimeMs.nextDelay()};
|
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)
|
static void Discord_UpdateConnection(void)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
if (!Connection) {
|
if (!Connection)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (!Connection->IsOpen()) {
|
if (!Connection->IsOpen())
|
||||||
if (std::chrono::system_clock::now() >= NextConnect) {
|
{
|
||||||
|
if (std::chrono::system_clock::now() >= NextConnect)
|
||||||
|
{
|
||||||
UpdateReconnectTime();
|
UpdateReconnectTime();
|
||||||
Connection->Open();
|
Connection->Open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
// reads
|
// reads
|
||||||
|
|
||||||
for (;;) {
|
for (;;)
|
||||||
|
{
|
||||||
JsonDocument message;
|
JsonDocument message;
|
||||||
|
|
||||||
if (!Connection->Read(message)) {
|
if (!Connection->Read(message))
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
const char* evtName = GetStrMember(&message, "evt");
|
const char* evtName = GetStrMember(&message, "evt");
|
||||||
const char* nonce = GetStrMember(&message, "nonce");
|
const char* nonce = GetStrMember(&message, "nonce");
|
||||||
|
|
||||||
if (nonce) {
|
if (nonce)
|
||||||
|
{
|
||||||
// in responses only -- should use to match up response when needed.
|
// 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");
|
auto data = GetObjMember(&message, "data");
|
||||||
LastErrorCode = GetIntMember(data, "code");
|
LastErrorCode = GetIntMember(data, "code");
|
||||||
StringCopy(LastErrorMessage, GetStrMember(data, "message", ""));
|
StringCopy(LastErrorMessage, GetStrMember(data, "message", ""));
|
||||||
GotErrorMessage.store(true);
|
GotErrorMessage.store(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
// should have evt == name of event, optional data
|
// should have evt == name of event, optional data
|
||||||
if (evtName == nullptr) {
|
if (evtName == nullptr)
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
auto data = GetObjMember(&message, "data");
|
auto data = GetObjMember(&message, "data");
|
||||||
|
|
||||||
if (strcmp(evtName, "ACTIVITY_JOIN") == 0) {
|
if (strcmp(evtName, "ACTIVITY_JOIN") == 0)
|
||||||
|
{
|
||||||
auto secret = GetStrMember(data, "secret");
|
auto secret = GetStrMember(data, "secret");
|
||||||
if (secret) {
|
if (secret)
|
||||||
|
{
|
||||||
StringCopy(JoinGameSecret, secret);
|
StringCopy(JoinGameSecret, secret);
|
||||||
WasJoinGame.store(true);
|
WasJoinGame.store(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0) {
|
else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0)
|
||||||
auto secret = GetStrMember(data, "secret");
|
{
|
||||||
if (secret) {
|
auto secret = GetStrMember(data, "secret");
|
||||||
StringCopy(SpectateGameSecret, secret);
|
if (secret)
|
||||||
WasSpectateGame.store(true);
|
{
|
||||||
}
|
StringCopy(SpectateGameSecret, secret);
|
||||||
|
WasSpectateGame.store(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (strcmp(evtName, "ACTIVITY_JOIN_REQUEST") == 0) {
|
else if (strcmp(evtName, "ACTIVITY_JOIN_REQUEST") == 0)
|
||||||
auto user = GetObjMember(data, "user");
|
{
|
||||||
auto userId = GetStrMember(user, "id");
|
auto user = GetObjMember(data, "user");
|
||||||
auto username = GetStrMember(user, "username");
|
auto userId = GetStrMember(user, "id");
|
||||||
auto avatar = GetStrMember(user, "avatar");
|
auto username = GetStrMember(user, "username");
|
||||||
auto joinReq = JoinAskQueue.GetNextAddMessage();
|
auto avatar = GetStrMember(user, "avatar");
|
||||||
if (userId && username && joinReq) {
|
auto joinReq = JoinAskQueue.GetNextAddMessage();
|
||||||
StringCopy(joinReq->userId, userId);
|
if (userId && username && joinReq)
|
||||||
StringCopy(joinReq->username, username);
|
{
|
||||||
auto discriminator = GetStrMember(user, "discriminator");
|
StringCopy(joinReq->userId, userId);
|
||||||
if (discriminator) {
|
StringCopy(joinReq->username, username);
|
||||||
StringCopy(joinReq->discriminator, discriminator);
|
auto discriminator = GetStrMember(user, "discriminator");
|
||||||
}
|
if (discriminator)
|
||||||
if (avatar) {
|
StringCopy(joinReq->discriminator, discriminator);
|
||||||
StringCopy(joinReq->avatar, avatar);
|
if (avatar)
|
||||||
}
|
StringCopy(joinReq->avatar, avatar);
|
||||||
else {
|
else
|
||||||
joinReq->avatar[0] = 0;
|
joinReq->avatar[0] = 0;
|
||||||
}
|
JoinAskQueue.CommitAdd();
|
||||||
JoinAskQueue.CommitAdd();
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// writes
|
// writes
|
||||||
if (QueuedPresence.length) {
|
if (QueuedPresence.length)
|
||||||
QueuedMessage local;
|
{
|
||||||
{
|
QueuedMessage local;
|
||||||
std::lock_guard<std::mutex> guard(PresenceMutex);
|
{
|
||||||
local.Copy(QueuedPresence);
|
std::lock_guard<std::mutex> guard(PresenceMutex);
|
||||||
QueuedPresence.length = 0;
|
local.Copy(QueuedPresence);
|
||||||
}
|
QueuedPresence.length = 0;
|
||||||
if (!Connection->Write(local.buffer, local.length)) {
|
}
|
||||||
// if we fail to send, requeue
|
if (!Connection->Write(local.buffer, local.length)) {
|
||||||
std::lock_guard<std::mutex> guard(PresenceMutex);
|
// if we fail to send, requeue
|
||||||
QueuedPresence.Copy(local);
|
std::lock_guard<std::mutex> guard(PresenceMutex);
|
||||||
}
|
QueuedPresence.Copy(local);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (SendQueue.HavePendingSends()) {
|
while (SendQueue.HavePendingSends())
|
||||||
|
{
|
||||||
auto qmessage = SendQueue.GetNextSendMessage();
|
auto qmessage = SendQueue.GetNextSendMessage();
|
||||||
Connection->Write(qmessage->buffer, qmessage->length);
|
Connection->Write(qmessage->buffer, qmessage->length);
|
||||||
SendQueue.CommitSend();
|
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)
|
static bool RegisterForEvent(const char* evtName)
|
||||||
{
|
{
|
||||||
auto qmessage = SendQueue.GetNextAddMessage();
|
auto qmessage = SendQueue.GetNextAddMessage();
|
||||||
|
@ -250,7 +252,8 @@ static bool RegisterForEvent(const char* evtName)
|
||||||
qmessage->length =
|
qmessage->length =
|
||||||
JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
||||||
SendQueue.CommitAdd();
|
SendQueue.CommitAdd();
|
||||||
SignalIOActivity();
|
if (IoThread)
|
||||||
|
IoThread->Notify();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -263,7 +266,8 @@ static bool DeregisterForEvent(const char* evtName)
|
||||||
qmessage->length =
|
qmessage->length =
|
||||||
JsonWriteUnsubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
JsonWriteUnsubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
||||||
SendQueue.CommitAdd();
|
SendQueue.CommitAdd();
|
||||||
SignalIOActivity();
|
if (IoThread)
|
||||||
|
IoThread->Notify();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -308,31 +312,31 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
||||||
}
|
}
|
||||||
|
|
||||||
Connection = RpcConnection::Create(applicationId);
|
Connection = RpcConnection::Create(applicationId);
|
||||||
Connection->onConnect = [](JsonDocument& readyMessage) {
|
Connection->onConnect = [](JsonDocument& readyMessage)
|
||||||
|
{
|
||||||
Discord_UpdateHandlers(&QueuedHandlers);
|
Discord_UpdateHandlers(&QueuedHandlers);
|
||||||
auto data = GetObjMember(&readyMessage, "data");
|
auto data = GetObjMember(&readyMessage, "data");
|
||||||
auto user = GetObjMember(data, "user");
|
auto user = GetObjMember(data, "user");
|
||||||
auto userId = GetStrMember(user, "id");
|
auto userId = GetStrMember(user, "id");
|
||||||
auto username = GetStrMember(user, "username");
|
auto username = GetStrMember(user, "username");
|
||||||
auto avatar = GetStrMember(user, "avatar");
|
auto avatar = GetStrMember(user, "avatar");
|
||||||
if (userId && username) {
|
if (userId && username)
|
||||||
|
{
|
||||||
StringCopy(connectedUser.userId, userId);
|
StringCopy(connectedUser.userId, userId);
|
||||||
StringCopy(connectedUser.username, username);
|
StringCopy(connectedUser.username, username);
|
||||||
auto discriminator = GetStrMember(user, "discriminator");
|
auto discriminator = GetStrMember(user, "discriminator");
|
||||||
if (discriminator) {
|
if (discriminator)
|
||||||
StringCopy(connectedUser.discriminator, discriminator);
|
StringCopy(connectedUser.discriminator, discriminator);
|
||||||
}
|
if (avatar)
|
||||||
if (avatar) {
|
|
||||||
StringCopy(connectedUser.avatar, avatar);
|
StringCopy(connectedUser.avatar, avatar);
|
||||||
}
|
else
|
||||||
else {
|
|
||||||
connectedUser.avatar[0] = 0;
|
connectedUser.avatar[0] = 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
WasJustConnected.exchange(true);
|
WasJustConnected.exchange(true);
|
||||||
ReconnectTimeMs.reset();
|
ReconnectTimeMs.reset();
|
||||||
};
|
};
|
||||||
Connection->onDisconnect = [](int err, const char* message) {
|
Connection->onDisconnect = [](int err, const char* message)
|
||||||
|
{
|
||||||
LastDisconnectErrorCode = err;
|
LastDisconnectErrorCode = err;
|
||||||
StringCopy(LastDisconnectErrorMessage, message);
|
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)
|
extern "C" DISCORD_EXPORT void Discord_Shutdown(void)
|
||||||
{
|
{
|
||||||
if (!Connection) {
|
if (!Connection)
|
||||||
return;
|
return;
|
||||||
}
|
Connection->onConnect = nullptr;
|
||||||
Connection->onConnect = nullptr;
|
|
||||||
Connection->onDisconnect = nullptr;
|
Connection->onDisconnect = nullptr;
|
||||||
Handlers = {};
|
Handlers = {};
|
||||||
if (IoThread != nullptr) {
|
if (IoThread != nullptr)
|
||||||
|
{
|
||||||
IoThread->Stop();
|
IoThread->Stop();
|
||||||
delete IoThread;
|
delete IoThread;
|
||||||
IoThread = nullptr;
|
IoThread = nullptr;
|
||||||
|
@ -370,7 +374,8 @@ extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence*
|
||||||
QueuedPresence.length = JsonWriteRichPresenceObj(
|
QueuedPresence.length = JsonWriteRichPresenceObj(
|
||||||
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
|
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
|
||||||
}
|
}
|
||||||
SignalIOActivity();
|
if (IoThread)
|
||||||
|
IoThread->Notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" DISCORD_EXPORT void Discord_ClearPresence(void)
|
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)
|
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 we are not connected, let's not batch up stale messages for later
|
||||||
if (!Connection || !Connection->IsOpen()) {
|
if (!Connection || !Connection->IsOpen())
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
auto qmessage = SendQueue.GetNextAddMessage();
|
auto qmessage = SendQueue.GetNextAddMessage();
|
||||||
if (qmessage) {
|
if (qmessage)
|
||||||
|
{
|
||||||
qmessage->length =
|
qmessage->length =
|
||||||
JsonWriteJoinReply(qmessage->buffer, sizeof(qmessage->buffer), userId, reply, Nonce++);
|
JsonWriteJoinReply(qmessage->buffer, sizeof(qmessage->buffer), userId, reply, Nonce++);
|
||||||
SendQueue.CommitAdd();
|
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
|
// 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.
|
// signals are book-ended by calls to ready and disconnect.
|
||||||
|
|
||||||
if (!Connection) {
|
if (!Connection)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
bool wasDisconnected = WasJustDisconnected.exchange(false);
|
bool wasDisconnected = WasJustDisconnected.exchange(false);
|
||||||
bool isConnected = Connection->IsOpen();
|
bool isConnected = Connection->IsOpen();
|
||||||
|
|
||||||
if (isConnected) {
|
if (isConnected)
|
||||||
|
{
|
||||||
// if we are connected, disconnect cb first
|
// if we are connected, disconnect cb first
|
||||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
if (wasDisconnected && Handlers.disconnected) {
|
if (wasDisconnected && Handlers.disconnected)
|
||||||
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WasJustConnected.exchange(false)) {
|
if (WasJustConnected.exchange(false))
|
||||||
|
{
|
||||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
if (Handlers.ready) {
|
if (Handlers.ready)
|
||||||
|
{
|
||||||
DiscordUser du{connectedUser.userId,
|
DiscordUser du{connectedUser.userId,
|
||||||
connectedUser.username,
|
connectedUser.username,
|
||||||
connectedUser.discriminator,
|
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);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
if (Handlers.errored) {
|
if (Handlers.errored)
|
||||||
Handlers.errored(LastErrorCode, LastErrorMessage);
|
Handlers.errored(LastErrorCode, LastErrorMessage);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WasJoinGame.exchange(false)) {
|
if (WasJoinGame.exchange(false))
|
||||||
|
{
|
||||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
if (Handlers.joinGame) {
|
if (Handlers.joinGame)
|
||||||
Handlers.joinGame(JoinGameSecret);
|
Handlers.joinGame(JoinGameSecret);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WasSpectateGame.exchange(false)) {
|
if (WasSpectateGame.exchange(false))
|
||||||
|
{
|
||||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
if (Handlers.spectateGame) {
|
if (Handlers.spectateGame)
|
||||||
Handlers.spectateGame(SpectateGameSecret);
|
Handlers.spectateGame(SpectateGameSecret);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right now this batches up any requests and sends them all in a burst; I could imagine a world
|
// 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
|
// 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
|
// 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.
|
// not it should be trivial for the implementer to make a queue themselves.
|
||||||
while (JoinAskQueue.HavePendingSends()) {
|
while (JoinAskQueue.HavePendingSends())
|
||||||
|
{
|
||||||
auto req = JoinAskQueue.GetNextSendMessage();
|
auto req = JoinAskQueue.GetNextSendMessage();
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
if (Handlers.joinRequest) {
|
if (Handlers.joinRequest)
|
||||||
|
{
|
||||||
DiscordUser du{req->userId, req->username, req->discriminator, req->avatar};
|
DiscordUser du{req->userId, req->username, req->discriminator, req->avatar};
|
||||||
Handlers.joinRequest(&du);
|
Handlers.joinRequest(&du);
|
||||||
}
|
}
|
||||||
|
@ -463,38 +472,37 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
|
||||||
JoinAskQueue.CommitSend();
|
JoinAskQueue.CommitSend();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isConnected) {
|
if (!isConnected)
|
||||||
|
{
|
||||||
// if we are not connected, disconnect message last
|
// if we are not connected, disconnect message last
|
||||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
if (wasDisconnected && Handlers.disconnected) {
|
if (wasDisconnected && Handlers.disconnected)
|
||||||
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
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)
|
extern "C" DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* newHandlers)
|
||||||
{
|
{
|
||||||
if (newHandlers) {
|
if (newHandlers)
|
||||||
#define HANDLE_EVENT_REGISTRATION(handler_name, event) \
|
{
|
||||||
if (!Handlers.handler_name && newHandlers->handler_name) { \
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
RegisterForEvent(event); \
|
HANDLE_EVENT_REGISTRATION(joinGame, "ACTIVITY_JOIN");
|
||||||
} \
|
HANDLE_EVENT_REGISTRATION(spectateGame, "ACTIVITY_SPECTATE");
|
||||||
else if (Handlers.handler_name && !newHandlers->handler_name) { \
|
HANDLE_EVENT_REGISTRATION(joinRequest, "ACTIVITY_JOIN_REQUEST");
|
||||||
DeregisterForEvent(event); \
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
Handlers = *newHandlers;
|
||||||
HANDLE_EVENT_REGISTRATION(joinGame, "ACTIVITY_JOIN")
|
}
|
||||||
HANDLE_EVENT_REGISTRATION(spectateGame, "ACTIVITY_SPECTATE")
|
else
|
||||||
HANDLE_EVENT_REGISTRATION(joinRequest, "ACTIVITY_JOIN_REQUEST")
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
#undef HANDLE_EVENT_REGISTRATION
|
Handlers = {};
|
||||||
|
}
|
||||||
Handlers = *newHandlers;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
|
||||||
Handlers = {};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
// A simple queue. No locks, but only works with a single thread as producer and a single thread as
|
/* 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 consumer. Mutex up as needed. */
|
||||||
|
|
||||||
template <typename ElementType, size_t QueueSize>
|
template <typename ElementType, size_t QueueSize>
|
||||||
class MsgQueue {
|
class MsgQueue {
|
||||||
|
@ -17,10 +17,9 @@ public:
|
||||||
|
|
||||||
ElementType* GetNextAddMessage()
|
ElementType* GetNextAddMessage()
|
||||||
{
|
{
|
||||||
// if we are falling behind, bail
|
/* if we are falling behind, bail */
|
||||||
if (pendingSends_.load() >= QueueSize) {
|
if (pendingSends_.load() >= QueueSize)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
|
||||||
auto index = (nextAdd_++) % QueueSize;
|
auto index = (nextAdd_++) % QueueSize;
|
||||||
return &queue_[index];
|
return &queue_[index];
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,50 +22,49 @@ static RpcConnection Instance;
|
||||||
|
|
||||||
void RpcConnection::Open()
|
void RpcConnection::Open()
|
||||||
{
|
{
|
||||||
if (state == State::Connected) {
|
if (state == State::Connected)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (state == State::Disconnected)
|
||||||
|
{
|
||||||
|
if (!connection->Open())
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == State::Disconnected) {
|
if (state == State::SentHandshake)
|
||||||
if (connection->Open()) {
|
{
|
||||||
}
|
JsonDocument message;
|
||||||
else {
|
if (Read(message))
|
||||||
return;
|
{
|
||||||
}
|
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
|
||||||
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 {
|
|
||||||
sendFrame.opcode = Opcode::Handshake;
|
sendFrame.opcode = Opcode::Handshake;
|
||||||
sendFrame.length = (uint32_t)JsonWriteHandshakeObj(
|
sendFrame.length = (uint32_t)JsonWriteHandshakeObj(
|
||||||
sendFrame.message, sizeof(sendFrame.message), RpcVersion, appId);
|
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;
|
state = State::SentHandshake;
|
||||||
}
|
else
|
||||||
else {
|
|
||||||
Close();
|
Close();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpcConnection::Close()
|
void RpcConnection::Close()
|
||||||
{
|
{
|
||||||
if (onDisconnect && (state == State::Connected || state == State::SentHandshake)) {
|
if (onDisconnect && (state == State::Connected || state == State::SentHandshake))
|
||||||
onDisconnect(lastErrorCode, lastErrorMessage);
|
onDisconnect(lastErrorCode, lastErrorMessage);
|
||||||
}
|
|
||||||
connection->Close();
|
connection->Close();
|
||||||
state = State::Disconnected;
|
state = State::Disconnected;
|
||||||
}
|
}
|
||||||
|
@ -75,7 +74,8 @@ bool RpcConnection::Write(const void* data, size_t length)
|
||||||
sendFrame.opcode = Opcode::Frame;
|
sendFrame.opcode = Opcode::Frame;
|
||||||
memcpy(sendFrame.message, data, length);
|
memcpy(sendFrame.message, data, length);
|
||||||
sendFrame.length = (uint32_t)length;
|
sendFrame.length = (uint32_t)length;
|
||||||
if (!connection->Write(&sendFrame, sizeof(MessageFrameHeader) + length)) {
|
if (!connection->Write(&sendFrame, sizeof(MessageFrameHeader) + length))
|
||||||
|
{
|
||||||
Close();
|
Close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -84,14 +84,16 @@ bool RpcConnection::Write(const void* data, size_t length)
|
||||||
|
|
||||||
bool RpcConnection::Read(JsonDocument& message)
|
bool RpcConnection::Read(JsonDocument& message)
|
||||||
{
|
{
|
||||||
if (state != State::Connected && state != State::SentHandshake) {
|
if (state != State::Connected && state != State::SentHandshake)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
MessageFrame readFrame;
|
MessageFrame readFrame;
|
||||||
for (;;) {
|
for (;;)
|
||||||
|
{
|
||||||
bool didRead = connection->Read(&readFrame, sizeof(MessageFrameHeader));
|
bool didRead = connection->Read(&readFrame, sizeof(MessageFrameHeader));
|
||||||
if (!didRead) {
|
if (!didRead)
|
||||||
if (!connection->isOpen) {
|
{
|
||||||
|
if (!connection->isOpen)
|
||||||
|
{
|
||||||
lastErrorCode = (int)ErrorCode::PipeClosed;
|
lastErrorCode = (int)ErrorCode::PipeClosed;
|
||||||
StringCopy(lastErrorMessage, "Pipe closed");
|
StringCopy(lastErrorMessage, "Pipe closed");
|
||||||
Close();
|
Close();
|
||||||
|
@ -99,9 +101,11 @@ bool RpcConnection::Read(JsonDocument& message)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readFrame.length > 0) {
|
if (readFrame.length > 0)
|
||||||
|
{
|
||||||
didRead = connection->Read(readFrame.message, readFrame.length);
|
didRead = connection->Read(readFrame.message, readFrame.length);
|
||||||
if (!didRead) {
|
if (!didRead)
|
||||||
|
{
|
||||||
lastErrorCode = (int)ErrorCode::ReadCorrupt;
|
lastErrorCode = (int)ErrorCode::ReadCorrupt;
|
||||||
StringCopy(lastErrorMessage, "Partial data in frame");
|
StringCopy(lastErrorMessage, "Partial data in frame");
|
||||||
Close();
|
Close();
|
||||||
|
@ -110,32 +114,33 @@ bool RpcConnection::Read(JsonDocument& message)
|
||||||
readFrame.message[readFrame.length] = 0;
|
readFrame.message[readFrame.length] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (readFrame.opcode) {
|
switch (readFrame.opcode)
|
||||||
case Opcode::Close: {
|
{
|
||||||
message.ParseInsitu(readFrame.message);
|
case Opcode::Close:
|
||||||
lastErrorCode = GetIntMember(&message, "code");
|
{
|
||||||
StringCopy(lastErrorMessage, GetStrMember(&message, "message", ""));
|
message.ParseInsitu(readFrame.message);
|
||||||
Close();
|
lastErrorCode = GetIntMember(&message, "code");
|
||||||
return false;
|
StringCopy(lastErrorMessage, GetStrMember(&message, "message", ""));
|
||||||
}
|
Close();
|
||||||
case Opcode::Frame:
|
return false;
|
||||||
message.ParseInsitu(readFrame.message);
|
}
|
||||||
return true;
|
case Opcode::Frame:
|
||||||
case Opcode::Ping:
|
message.ParseInsitu(readFrame.message);
|
||||||
readFrame.opcode = Opcode::Pong;
|
return true;
|
||||||
if (!connection->Write(&readFrame, sizeof(MessageFrameHeader) + readFrame.length)) {
|
case Opcode::Ping:
|
||||||
Close();
|
readFrame.opcode = Opcode::Pong;
|
||||||
}
|
if (!connection->Write(&readFrame, sizeof(MessageFrameHeader) + readFrame.length))
|
||||||
break;
|
Close();
|
||||||
case Opcode::Pong:
|
break;
|
||||||
break;
|
case Opcode::Pong:
|
||||||
case Opcode::Handshake:
|
break;
|
||||||
default:
|
case Opcode::Handshake:
|
||||||
// something bad happened
|
default:
|
||||||
lastErrorCode = (int)ErrorCode::ReadCorrupt;
|
// something bad happened
|
||||||
StringCopy(lastErrorMessage, "Bad ipc frame");
|
lastErrorCode = (int)ErrorCode::ReadCorrupt;
|
||||||
Close();
|
StringCopy(lastErrorMessage, "Bad ipc frame");
|
||||||
return false;
|
Close();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1526,6 +1526,15 @@ HTTP SERVER
|
||||||
============================================================ */
|
============================================================ */
|
||||||
#if defined(HAVE_DISCORD)
|
#if defined(HAVE_DISCORD)
|
||||||
#include "../discord/discord.c"
|
#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
|
#endif
|
||||||
|
|
||||||
/*============================================================
|
/*============================================================
|
||||||
|
|
|
@ -144,12 +144,8 @@ FONTS
|
||||||
#include "../deps/discord-rpc/src/serialization.cpp"
|
#include "../deps/discord-rpc/src/serialization.cpp"
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#include "../deps/discord-rpc/src/discord_register_win.cpp"
|
|
||||||
#include "../deps/discord-rpc/src/connection_win.cpp"
|
#include "../deps/discord-rpc/src/connection_win.cpp"
|
||||||
#endif
|
#endif
|
||||||
#if defined(__linux__)
|
|
||||||
#include "../deps/discord-rpc/src/discord_register_linux.cpp"
|
|
||||||
#endif
|
|
||||||
#if defined(__unix__) || defined(__APPLE__)
|
#if defined(__unix__) || defined(__APPLE__)
|
||||||
#include "../deps/discord-rpc/src/connection_unix.cpp"
|
#include "../deps/discord-rpc/src/connection_unix.cpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue