discord-rpc: Formatting

This commit is contained in:
Jonas Kvinge
2025-04-11 23:27:40 +02:00
parent f9e4f9a09a
commit 634f6ea9f5
20 changed files with 291 additions and 290 deletions

View File

@@ -1 +1,41 @@
add_subdirectory(src) set(DISCORD_RPC_SOURCES
discord_rpc.h
discord_register.h
discord_rpc.cpp
discord_rpc_connection.h
discord_rpc_connection.cpp
discord_serialization.h
discord_serialization.cpp
discord_connection.h
discord_backoff.h
discord_msg_queue.h
)
if(UNIX)
list(APPEND DISCORD_RPC_SOURCES discord_connection_unix.cpp)
if(APPLE)
list(APPEND DISCORD_RPC_SOURCES discord_register_osx.m)
add_definitions(-DDISCORD_OSX)
else()
list(APPEND DISCORD_RPC_SOURCES discord_register_linux.cpp)
add_definitions(-DDISCORD_LINUX)
endif()
endif()
if(WIN32)
list(APPEND DISCORD_RPC_SOURCES discord_connection_win.cpp discord_register_win.cpp)
add_definitions(-DDISCORD_WINDOWS)
endif()
add_library(discord-rpc STATIC ${DISCORD_RPC_SOURCES})
if(APPLE)
target_link_libraries(discord-rpc PRIVATE "-framework AppKit")
endif()
if(WIN32)
target_link_libraries(discord-rpc PRIVATE psapi advapi32)
endif()
target_include_directories(discord-rpc SYSTEM PRIVATE ${RapidJSON_INCLUDE_DIRS})
target_include_directories(discord-rpc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -12,7 +12,7 @@ int GetProcessId();
struct BaseConnection { struct BaseConnection {
static BaseConnection *Create(); static BaseConnection *Create();
static void Destroy(BaseConnection *&); static void Destroy(BaseConnection *&);
bool isOpen { false }; bool isOpen = false;
bool Open(); bool Open();
bool Close(); bool Close();
bool Write(const void *data, size_t length); bool Write(const void *data, size_t length);

View File

@@ -1,4 +1,4 @@
#include "connection.h" #include "discord_connection.h"
#include <cerrno> #include <cerrno>
#include <fcntl.h> #include <fcntl.h>
@@ -28,28 +28,34 @@ static int MsgFlags = 0;
#endif #endif
static const char *GetTempPath() { static const char *GetTempPath() {
const char *temp = getenv("XDG_RUNTIME_DIR"); const char *temp = getenv("XDG_RUNTIME_DIR");
temp = temp ? temp : getenv("TMPDIR"); temp = temp ? temp : getenv("TMPDIR");
temp = temp ? temp : getenv("TMP"); temp = temp ? temp : getenv("TMP");
temp = temp ? temp : getenv("TEMP"); temp = temp ? temp : getenv("TEMP");
temp = temp ? temp : "/tmp"; temp = temp ? temp : "/tmp";
return temp; return temp;
} }
/*static*/ BaseConnection *BaseConnection::Create() { BaseConnection *BaseConnection::Create() {
PipeAddr.sun_family = AF_UNIX; PipeAddr.sun_family = AF_UNIX;
return &Connection; return &Connection;
} }
/*static*/ void BaseConnection::Destroy(BaseConnection *&c) { void BaseConnection::Destroy(BaseConnection *&c) {
auto self = reinterpret_cast<BaseConnectionUnix *>(c);
auto self = reinterpret_cast<BaseConnectionUnix*>(c);
self->Close(); self->Close();
c = nullptr; c = nullptr;
} }
bool BaseConnection::Open() { bool BaseConnection::Open() {
const char *tempPath = GetTempPath(); const char *tempPath = GetTempPath();
auto self = reinterpret_cast<BaseConnectionUnix *>(this); auto self = reinterpret_cast<BaseConnectionUnix*>(this);
self->sock = socket(AF_UNIX, SOCK_STREAM, 0); self->sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (self->sock == -1) { if (self->sock == -1) {
return false; return false;
@@ -61,8 +67,7 @@ bool BaseConnection::Open() {
#endif #endif
for (int pipeNum = 0; pipeNum < 10; ++pipeNum) { for (int pipeNum = 0; pipeNum < 10; ++pipeNum) {
snprintf( snprintf(PipeAddr.sun_path, sizeof(PipeAddr.sun_path), "%s/discord-ipc-%d", tempPath, pipeNum);
PipeAddr.sun_path, sizeof(PipeAddr.sun_path), "%s/discord-ipc-%d", tempPath, pipeNum);
int err = connect(self->sock, reinterpret_cast<const sockaddr*>(&PipeAddr), sizeof(PipeAddr)); int err = connect(self->sock, reinterpret_cast<const sockaddr*>(&PipeAddr), sizeof(PipeAddr));
if (err == 0) { if (err == 0) {
self->isOpen = true; self->isOpen = true;
@@ -70,10 +75,13 @@ bool BaseConnection::Open() {
} }
} }
self->Close(); self->Close();
return false; return false;
} }
bool BaseConnection::Close() { bool BaseConnection::Close() {
auto self = reinterpret_cast<BaseConnectionUnix *>(this); auto self = reinterpret_cast<BaseConnectionUnix *>(this);
if (self->sock == -1) { if (self->sock == -1) {
return false; return false;
@@ -81,11 +89,14 @@ bool BaseConnection::Close() {
close(self->sock); close(self->sock);
self->sock = -1; self->sock = -1;
self->isOpen = false; self->isOpen = false;
return true; return true;
} }
bool BaseConnection::Write(const void *data, size_t length) { bool BaseConnection::Write(const void *data, size_t length) {
auto self = reinterpret_cast<BaseConnectionUnix *>(this);
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
if (self->sock == -1) { if (self->sock == -1) {
return false; return false;
@@ -95,11 +106,14 @@ bool BaseConnection::Write(const void *data, size_t length) {
if (sentBytes < 0) { if (sentBytes < 0) {
Close(); Close();
} }
return sentBytes == static_cast<ssize_t>(length); return sentBytes == static_cast<ssize_t>(length);
} }
bool BaseConnection::Read(void *data, size_t length) { bool BaseConnection::Read(void *data, size_t length) {
auto self = reinterpret_cast<BaseConnectionUnix *>(this);
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
if (self->sock == -1) { if (self->sock == -1) {
return false; return false;
@@ -115,7 +129,9 @@ bool BaseConnection::Read(void *data, size_t length) {
else if (res == 0) { else if (res == 0) {
Close(); Close();
} }
return static_cast<size_t>(res) == length; return static_cast<size_t>(res) == length;
} }
} // namespace discord_rpc } // namespace discord_rpc

View File

@@ -1,9 +1,10 @@
#include "connection.h" #include "discord_connection.h"
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#define NOMCX #define NOMCX
#define NOSERVICE #define NOSERVICE
#define NOIME #define NOIME
#include <cassert> #include <cassert>
#include <windows.h> #include <windows.h>
@@ -19,24 +20,26 @@ struct BaseConnectionWin : public BaseConnection {
static BaseConnectionWin Connection; static BaseConnectionWin Connection;
/*static*/ BaseConnection *BaseConnection::Create() { BaseConnection *BaseConnection::Create() {
return &Connection; return &Connection;
} }
/*static*/ void BaseConnection::Destroy(BaseConnection *&c) { void BaseConnection::Destroy(BaseConnection *&c) {
auto self = reinterpret_cast<BaseConnectionWin*>(c); auto self = reinterpret_cast<BaseConnectionWin*>(c);
self->Close(); self->Close();
c = nullptr; c = nullptr;
} }
bool BaseConnection::Open() { bool BaseConnection::Open() {
wchar_t pipeName[] { L"\\\\?\\pipe\\discord-ipc-0" }; wchar_t pipeName[] { L"\\\\?\\pipe\\discord-ipc-0" };
const size_t pipeDigit = sizeof(pipeName) / sizeof(wchar_t) - 2; const size_t pipeDigit = sizeof(pipeName) / sizeof(wchar_t) - 2;
pipeName[pipeDigit] = L'0'; pipeName[pipeDigit] = L'0';
auto self = reinterpret_cast<BaseConnectionWin *>(this); auto self = reinterpret_cast<BaseConnectionWin *>(this);
for (;;) { for (;;) {
self->pipe = ::CreateFileW( self->pipe = ::CreateFileW(pipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
pipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if (self->pipe != INVALID_HANDLE_VALUE) { if (self->pipe != INVALID_HANDLE_VALUE) {
self->isOpen = true; self->isOpen = true;
return true; return true;
@@ -57,17 +60,22 @@ bool BaseConnection::Open() {
} }
return false; return false;
} }
} }
bool BaseConnection::Close() { bool BaseConnection::Close() {
auto self = reinterpret_cast<BaseConnectionWin *>(this); auto self = reinterpret_cast<BaseConnectionWin *>(this);
::CloseHandle(self->pipe); ::CloseHandle(self->pipe);
self->pipe = INVALID_HANDLE_VALUE; self->pipe = INVALID_HANDLE_VALUE;
self->isOpen = false; self->isOpen = false;
return true; return true;
} }
bool BaseConnection::Write(const void *data, size_t length) { bool BaseConnection::Write(const void *data, size_t length) {
if (length == 0) { if (length == 0) {
return true; return true;
} }
@@ -85,11 +93,13 @@ bool BaseConnection::Write(const void *data, size_t length) {
} }
const DWORD bytesLength = static_cast<DWORD>(length); const DWORD bytesLength = static_cast<DWORD>(length);
DWORD bytesWritten = 0; DWORD bytesWritten = 0;
return ::WriteFile(self->pipe, data, bytesLength, &bytesWritten, nullptr) == TRUE &&
bytesWritten == bytesLength; return ::WriteFile(self->pipe, data, bytesLength, &bytesWritten, nullptr) == TRUE && bytesWritten == bytesLength;
} }
bool BaseConnection::Read(void *data, size_t length) { bool BaseConnection::Read(void *data, size_t length) {
assert(data); assert(data);
if (!data) { if (!data) {
return false; return false;
@@ -119,7 +129,9 @@ bool BaseConnection::Read(void *data, size_t length) {
else { else {
Close(); Close();
} }
return false; return false;
} }
} // namespace discord_rpc } // namespace discord_rpc

View File

@@ -5,7 +5,6 @@ extern "C" {
#endif #endif
void Discord_Register(const char *applicationId, const char *command); void Discord_Register(const char *applicationId, const char *command);
void Discord_RegisterSteamGame(const char *applicationId, const char *steamId);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -24,8 +24,9 @@ static bool Mkdir(const char *path) {
} // namespace } // namespace
// we want to register games so we can run them from Discord client as discord-<appid>:// // We want to register games so we can run them from Discord client as discord-<appid>://
extern "C" void Discord_Register(const char *applicationId, const char *command) { extern "C" 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. // Add a desktop file and update some mime handlers so that xdg-open does the right thing.
const char *home = getenv("HOME"); const char *home = getenv("HOME");
@@ -33,9 +34,9 @@ extern "C" void Discord_Register(const char *applicationId, const char *command)
return; return;
} }
char exePath[1024]; char exePath[1024]{};
if (!command || !command[0]) { if (!command || !command[0]) {
ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath)); const ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath));
if (size <= 0 || size >= static_cast<ssize_t>(sizeof(exePath))) { if (size <= 0 || size >= static_cast<ssize_t>(sizeof(exePath))) {
return; return;
} }
@@ -50,17 +51,16 @@ extern "C" void Discord_Register(const char *applicationId, const char *command)
"NoDisplay=true\n" "NoDisplay=true\n"
"Categories=Discord;Games;\n" "Categories=Discord;Games;\n"
"MimeType=x-scheme-handler/discord-%s;\n"; "MimeType=x-scheme-handler/discord-%s;\n";
char desktopFile[2048]; char desktopFile[2048]{};
int fileLen = snprintf( int fileLen = snprintf(desktopFile, sizeof(desktopFile), desktopFileFormat, applicationId, command, applicationId);
desktopFile, sizeof(desktopFile), desktopFileFormat, applicationId, command, applicationId);
if (fileLen <= 0) { if (fileLen <= 0) {
return; return;
} }
char desktopFilename[256]; char desktopFilename[256]{};
(void)snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId); (void)snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId);
char desktopFilePath[1024]; char desktopFilePath[1024]{};
(void)snprintf(desktopFilePath, sizeof(desktopFilePath), "%s/.local", home); (void)snprintf(desktopFilePath, sizeof(desktopFilePath), "%s/.local", home);
if (!Mkdir(desktopFilePath)) { if (!Mkdir(desktopFilePath)) {
return; return;
@@ -84,7 +84,7 @@ extern "C" void Discord_Register(const char *applicationId, const char *command)
return; return;
} }
char xdgMimeCommand[1024]; char xdgMimeCommand[1024]{};
snprintf(xdgMimeCommand, snprintf(xdgMimeCommand,
sizeof(xdgMimeCommand), sizeof(xdgMimeCommand),
"xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s", "xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s",
@@ -93,11 +93,5 @@ extern "C" void Discord_Register(const char *applicationId, const char *command)
if (system(xdgMimeCommand) < 0) { if (system(xdgMimeCommand) < 0) {
fprintf(stderr, "Failed to register mime handler\n"); fprintf(stderr, "Failed to register mime handler\n");
} }
}
extern "C" 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,76 @@
#include <stdio.h>
#include <sys/stat.h>
#import <AppKit/AppKit.h>
#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
// to a file in the Discord config path, and when it is needed, Discord can try to load the file there, open
// the command therein (will pass to js's window.open, so requires a url-like thing)
// Note: will not work for sandboxed apps
NSString *home = NSHomeDirectory();
if (!home) {
return;
}
NSString *path = [[[[[[home stringByAppendingPathComponent:@"Library"]
stringByAppendingPathComponent:@"Application Support"]
stringByAppendingPathComponent:@"discord"]
stringByAppendingPathComponent:@"games"]
stringByAppendingPathComponent:[NSString stringWithUTF8String:applicationId]]
stringByAppendingPathExtension:@"json"];
[[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
NSString *jsonBuffer = [NSString stringWithFormat:@"{\"command\": \"%s\"}", command];
[jsonBuffer writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil];
}
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];
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;
}
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);
}
}
void Discord_Register(const char *applicationId, const char *command) {
if (command) {
RegisterCommand(applicationId, command);
}
else {
// raii lite
@autoreleasepool {
RegisterURL(applicationId);
}
}
}

View File

@@ -46,12 +46,8 @@ static HRESULT StringCbPrintfW(LPWSTR pszDest, size_t cbDest, LPCWSTR pszFormat,
#endif #endif
#define RegSetKeyValueW regset #define RegSetKeyValueW regset
static LSTATUS regset(HKEY hkey, static LSTATUS regset(HKEY hkey, LPCWSTR subkey, LPCWSTR name, DWORD type, const void *data, DWORD len) {
LPCWSTR subkey,
LPCWSTR name,
DWORD type,
const void *data,
DWORD len) {
HKEY htkey = hkey, hsubkey = nullptr; HKEY htkey = hkey, hsubkey = nullptr;
LSTATUS ret; LSTATUS ret;
if (subkey && subkey[0]) { if (subkey && subkey[0]) {
@@ -64,16 +60,18 @@ static LSTATUS regset(HKEY hkey,
if (hsubkey && hsubkey != hkey) if (hsubkey && hsubkey != hkey)
RegCloseKey(hsubkey); RegCloseKey(hsubkey);
return ret; return ret;
} }
static void Discord_RegisterW(const wchar_t *applicationId, const wchar_t *command) { static void Discord_RegisterW(const wchar_t *applicationId, const wchar_t *command) {
// https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx // 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>:// // 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. // Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions.
wchar_t exeFilePath[MAX_PATH]; wchar_t exeFilePath[MAX_PATH]{};
DWORD exeLen = GetModuleFileNameW(nullptr, exeFilePath, MAX_PATH); DWORD exeLen = GetModuleFileNameW(nullptr, exeFilePath, MAX_PATH);
wchar_t openCommand[1024]; wchar_t openCommand[1024]{};
if (command && command[0]) { if (command && command[0]) {
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command); StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command);
@@ -83,18 +81,16 @@ static void Discord_RegisterW(const wchar_t *applicationId, const wchar_t *comma
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", exeFilePath); StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", exeFilePath);
} }
wchar_t protocolName[64]; wchar_t protocolName[64]{};
StringCbPrintfW(protocolName, sizeof(protocolName), L"discord-%s", applicationId); StringCbPrintfW(protocolName, sizeof(protocolName), L"discord-%s", applicationId);
wchar_t protocolDescription[128]; wchar_t protocolDescription[128]{};
StringCbPrintfW( StringCbPrintfW(protocolDescription, sizeof(protocolDescription), L"URL:Run game %s protocol", applicationId);
protocolDescription, sizeof(protocolDescription), L"URL:Run game %s protocol", applicationId);
wchar_t urlProtocol = 0; wchar_t urlProtocol = 0;
wchar_t keyName[256]; wchar_t keyName[256]{};
StringCbPrintfW(keyName, sizeof(keyName), L"Software\\Classes\\%s", protocolName); StringCbPrintfW(keyName, sizeof(keyName), L"Software\\Classes\\%s", protocolName);
HKEY key; HKEY key;
auto status = auto status = RegCreateKeyExW(HKEY_CURRENT_USER, keyName, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr);
RegCreateKeyExW(HKEY_CURRENT_USER, keyName, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr);
if (status != ERROR_SUCCESS) { if (status != ERROR_SUCCESS) {
fprintf(stderr, "Error creating key\n"); fprintf(stderr, "Error creating key\n");
return; return;
@@ -102,8 +98,7 @@ static void Discord_RegisterW(const wchar_t *applicationId, const wchar_t *comma
DWORD len; DWORD len;
LSTATUS result; LSTATUS result;
len = static_cast<DWORD>(lstrlenW(protocolDescription) + 1); len = static_cast<DWORD>(lstrlenW(protocolDescription) + 1);
result = result = RegSetKeyValueW(key, nullptr, nullptr, REG_SZ, protocolDescription, len * sizeof(wchar_t));
RegSetKeyValueW(key, nullptr, nullptr, REG_SZ, protocolDescription, len * sizeof(wchar_t));
if (FAILED(result)) { if (FAILED(result)) {
fprintf(stderr, "Error writing description\n"); fprintf(stderr, "Error writing description\n");
} }
@@ -114,26 +109,26 @@ static void Discord_RegisterW(const wchar_t *applicationId, const wchar_t *comma
fprintf(stderr, "Error writing description\n"); fprintf(stderr, "Error writing description\n");
} }
result = RegSetKeyValueW( result = RegSetKeyValueW(key, L"DefaultIcon", nullptr, REG_SZ, exeFilePath, (exeLen + 1) * sizeof(wchar_t));
key, L"DefaultIcon", nullptr, REG_SZ, exeFilePath, (exeLen + 1) * sizeof(wchar_t));
if (FAILED(result)) { if (FAILED(result)) {
fprintf(stderr, "Error writing icon\n"); fprintf(stderr, "Error writing icon\n");
} }
len = static_cast<DWORD>(lstrlenW(openCommand) + 1); len = static_cast<DWORD>(lstrlenW(openCommand) + 1);
result = RegSetKeyValueW( result = RegSetKeyValueW(key, L"shell\\open\\command", nullptr, REG_SZ, openCommand, len * sizeof(wchar_t));
key, L"shell\\open\\command", nullptr, REG_SZ, openCommand, len * sizeof(wchar_t));
if (FAILED(result)) { if (FAILED(result)) {
fprintf(stderr, "Error writing command\n"); fprintf(stderr, "Error writing command\n");
} }
RegCloseKey(key); RegCloseKey(key);
} }
extern "C" void Discord_Register(const char *applicationId, const char *command) { extern "C" void Discord_Register(const char *applicationId, const char *command) {
wchar_t appId[32];
wchar_t appId[32]{};
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
wchar_t openCommand[1024]; wchar_t openCommand[1024]{};
const wchar_t *wcommand = nullptr; const wchar_t *wcommand = nullptr;
if (command && command[0]) { if (command && command[0]) {
const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand); const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand);
@@ -142,41 +137,6 @@ extern "C" void Discord_Register(const char *applicationId, const char *command)
} }
Discord_RegisterW(appId, wcommand); Discord_RegisterW(appId, wcommand);
} }
extern "C" 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

@@ -1,18 +1,16 @@
#include "discord_rpc.h"
#include "backoff.h"
#include "discord_register.h"
#include "msg_queue.h"
#include "rpc_connection.h"
#include "serialization.h"
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <mutex> #include <mutex>
#include <condition_variable> #include <condition_variable>
#include <thread> #include <thread>
#include "discord_rpc.h"
#include "discord_backoff.h"
#include "discord_register.h"
#include "discord_msg_queue.h"
#include "discord_rpc_connection.h"
#include "discord_serialization.h"
namespace discord_rpc { namespace discord_rpc {
constexpr size_t MaxMessageSize { 16 * 1024 }; constexpr size_t MaxMessageSize { 16 * 1024 };
@@ -67,8 +65,7 @@ static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
static MsgQueue<User, JoinQueueSize> JoinAskQueue; static MsgQueue<User, JoinQueueSize> JoinAskQueue;
static User connectedUser; static User connectedUser;
// We want to auto connect, and retry on failure, but not as fast as possible. This does expoential // We want to auto connect, and retry on failure, but not as fast as possible. This does expoential backoff from 0.5 seconds to 1 minute
// backoff from 0.5 seconds to 1 minute
static Backoff ReconnectTimeMs(500, 60 * 1000); static Backoff ReconnectTimeMs(500, 60 * 1000);
static auto NextConnect = std::chrono::system_clock::now(); static auto NextConnect = std::chrono::system_clock::now();
static int Pid { 0 }; static int Pid { 0 };
@@ -111,11 +108,13 @@ class IoThreadHolder {
static IoThreadHolder *IoThread { nullptr }; static IoThreadHolder *IoThread { nullptr };
static void UpdateReconnectTime() { static void UpdateReconnectTime() {
NextConnect = std::chrono::system_clock::now() +
std::chrono::duration<int64_t, std::milli> { ReconnectTimeMs.nextDelay() }; NextConnect = std::chrono::system_clock::now() + std::chrono::duration<int64_t, std::milli> { ReconnectTimeMs.nextDelay() };
} }
static void Discord_UpdateConnection(void) { static void Discord_UpdateConnection() {
if (!Connection) { if (!Connection) {
return; return;
} }
@@ -217,54 +216,54 @@ static void Discord_UpdateConnection(void) {
SendQueue.CommitSend(); SendQueue.CommitSend();
} }
} }
} }
static void SignalIOActivity() { static void SignalIOActivity() {
if (IoThread != nullptr) { if (IoThread != nullptr) {
IoThread->Notify(); IoThread->Notify();
} }
} }
static bool RegisterForEvent(const char *evtName) { static bool RegisterForEvent(const char *evtName) {
auto qmessage = SendQueue.GetNextAddMessage(); auto qmessage = SendQueue.GetNextAddMessage();
if (qmessage) { if (qmessage) {
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(); SignalIOActivity();
return true; return true;
} }
return false; return false;
} }
static bool DeregisterForEvent(const char *evtName) { static bool DeregisterForEvent(const char *evtName) {
auto qmessage = SendQueue.GetNextAddMessage(); auto qmessage = SendQueue.GetNextAddMessage();
if (qmessage) { if (qmessage) {
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(); SignalIOActivity();
return true; return true;
} }
return false; return false;
} }
extern "C" void Discord_Initialize(const char *applicationId, extern "C" void Discord_Initialize(const char *applicationId, DiscordEventHandlers *handlers, const int autoRegister) {
DiscordEventHandlers *handlers,
int autoRegister,
const char *optionalSteamId) {
IoThread = new (std::nothrow) IoThreadHolder(); IoThread = new (std::nothrow) IoThreadHolder();
if (IoThread == nullptr) { if (IoThread == nullptr) {
return; return;
} }
if (autoRegister) { if (autoRegister) {
if (optionalSteamId && optionalSteamId[0]) { Discord_Register(applicationId, nullptr);
Discord_RegisterSteamGame(applicationId, optionalSteamId);
}
else {
Discord_Register(applicationId, nullptr);
}
} }
Pid = GetProcessId(); Pid = GetProcessId();
@@ -323,9 +322,11 @@ extern "C" void Discord_Initialize(const char *applicationId,
}; };
IoThread->Start(); IoThread->Start();
} }
extern "C" void Discord_Shutdown(void) { extern "C" void Discord_Shutdown(void) {
if (!Connection) { if (!Connection) {
return; return;
} }
@@ -341,15 +342,19 @@ extern "C" void Discord_Shutdown(void) {
} }
RpcConnection::Destroy(Connection); RpcConnection::Destroy(Connection);
} }
extern "C" void Discord_UpdatePresence(const DiscordRichPresence *presence) { extern "C" void Discord_UpdatePresence(const DiscordRichPresence *presence) {
{ {
std::lock_guard<std::mutex> guard(PresenceMutex); std::lock_guard<std::mutex> guard(PresenceMutex);
QueuedPresence.length = JsonWriteRichPresenceObj(QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence); QueuedPresence.length = JsonWriteRichPresenceObj(QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
UpdatePresence.exchange(true); UpdatePresence.exchange(true);
} }
SignalIOActivity(); SignalIOActivity();
} }
extern "C" void Discord_ClearPresence(void) { extern "C" void Discord_ClearPresence(void) {
@@ -357,20 +362,22 @@ extern "C" void Discord_ClearPresence(void) {
} }
extern "C" void Discord_Respond(const char *userId, /* DISCORD_REPLY_ */ int reply) { extern "C" 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(); SignalIOActivity();
} }
} }
extern "C" void Discord_RunCallbacks(void) { extern "C" void Discord_RunCallbacks() {
// Note on some weirdness: internally we might connect, get other signals, disconnect any number // Note on some weirdness: internally we might connect, get other signals, disconnect any number
// 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.
@@ -379,8 +386,8 @@ extern "C" void Discord_RunCallbacks(void) {
return; return;
} }
bool wasDisconnected = WasJustDisconnected.exchange(false); const bool wasDisconnected = WasJustDisconnected.exchange(false);
bool isConnected = Connection->IsOpen(); const bool isConnected = Connection->IsOpen();
if (isConnected) { if (isConnected) {
// if we are connected, disconnect cb first // if we are connected, disconnect cb first
@@ -393,10 +400,7 @@ extern "C" void Discord_RunCallbacks(void) {
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.discriminator, connectedUser.avatar };
connectedUser.username,
connectedUser.discriminator,
connectedUser.avatar };
Handlers.ready(&du); Handlers.ready(&du);
} }
} }
@@ -428,7 +432,7 @@ extern "C" void Discord_RunCallbacks(void) {
// 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(); const auto req = JoinAskQueue.GetNextSendMessage();
{ {
std::lock_guard<std::mutex> guard(HandlerMutex); std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.joinRequest) { if (Handlers.joinRequest) {
@@ -446,9 +450,11 @@ extern "C" void Discord_RunCallbacks(void) {
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage); Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
} }
} }
} }
extern "C" void Discord_UpdateHandlers(DiscordEventHandlers *newHandlers) { extern "C" void Discord_UpdateHandlers(DiscordEventHandlers *newHandlers) {
if (newHandlers) { if (newHandlers) {
#define HANDLE_EVENT_REGISTRATION(handler_name, event) \ #define HANDLE_EVENT_REGISTRATION(handler_name, event) \
if (!Handlers.handler_name && newHandlers->handler_name) { \ if (!Handlers.handler_name && newHandlers->handler_name) { \
@@ -471,7 +477,7 @@ extern "C" void Discord_UpdateHandlers(DiscordEventHandlers *newHandlers) {
std::lock_guard<std::mutex> guard(HandlerMutex); std::lock_guard<std::mutex> guard(HandlerMutex);
Handlers = {}; Handlers = {};
} }
return;
} }
} // namespace discord_rpc } // namespace discord_rpc

View File

@@ -1,10 +1,6 @@
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
// clang-format off
// clang-format on
namespace discord_rpc { namespace discord_rpc {
#ifdef __cplusplus #ifdef __cplusplus
@@ -54,13 +50,10 @@ typedef struct DiscordEventHandlers {
#define DISCORD_PARTY_PRIVATE 0 #define DISCORD_PARTY_PRIVATE 0
#define DISCORD_PARTY_PUBLIC 1 #define DISCORD_PARTY_PUBLIC 1
void Discord_Initialize(const char *applicationId, void Discord_Initialize(const char *applicationId, DiscordEventHandlers *handlers, const int autoRegister);
DiscordEventHandlers *handlers,
int autoRegister,
const char *optionalSteamId);
void Discord_Shutdown(void); void Discord_Shutdown(void);
/* checks for incoming messages, dispatches callbacks */ // checks for incoming messages, dispatches callbacks
void Discord_RunCallbacks(void); void Discord_RunCallbacks(void);
void Discord_UpdatePresence(const DiscordRichPresence *presence); void Discord_UpdatePresence(const DiscordRichPresence *presence);

View File

@@ -1,24 +1,29 @@
#include "rpc_connection.h" #include "discord_rpc_connection.h"
#include "serialization.h" #include "discord_serialization.h"
namespace discord_rpc { namespace discord_rpc {
static const int RpcVersion = 1; static const int RpcVersion = 1;
static RpcConnection Instance; static RpcConnection Instance;
/*static*/ RpcConnection *RpcConnection::Create(const char *applicationId) { RpcConnection *RpcConnection::Create(const char *applicationId) {
Instance.connection = BaseConnection::Create(); Instance.connection = BaseConnection::Create();
StringCopy(Instance.appId, applicationId); StringCopy(Instance.appId, applicationId);
return &Instance; return &Instance;
} }
/*static*/ void RpcConnection::Destroy(RpcConnection *&c) { void RpcConnection::Destroy(RpcConnection *&c) {
c->Close(); c->Close();
BaseConnection::Destroy(c->connection); BaseConnection::Destroy(c->connection);
c = nullptr; c = nullptr;
} }
void RpcConnection::Open() { void RpcConnection::Open() {
if (state == State::Connected) { if (state == State::Connected) {
return; return;
} }
@@ -51,17 +56,21 @@ void RpcConnection::Open() {
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;
} }
bool RpcConnection::Write(const void *data, size_t length) { 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 = static_cast<uint32_t>(length); sendFrame.length = static_cast<uint32_t>(length);
@@ -69,14 +78,17 @@ bool RpcConnection::Write(const void *data, size_t length) {
Close(); Close();
return false; return false;
} }
return true; return true;
} }
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) {
@@ -127,6 +139,7 @@ bool RpcConnection::Read(JsonDocument &message) {
return false; return false;
} }
} }
} }
} // namespace discord_rpc } // namespace discord_rpc

View File

@@ -1,12 +1,11 @@
#pragma once #pragma once
#include "connection.h" #include "discord_connection.h"
#include "serialization.h" #include "discord_serialization.h"
namespace discord_rpc { namespace discord_rpc {
// I took this from the buffer size libuv uses for named pipes; I suspect ours would usually be much // I took this from the buffer size libuv uses for named pipes; I suspect ours would usually be much smaller.
// smaller.
constexpr size_t MaxRpcFrameSize = 64 * 1024; constexpr size_t MaxRpcFrameSize = 64 * 1024;
struct RpcConnection { struct RpcConnection {

View File

@@ -1,11 +1,12 @@
#include "serialization.h" #include "discord_serialization.h"
#include "connection.h" #include "discord_connection.h"
#include "discord_rpc.h" #include "discord_rpc.h"
namespace discord_rpc { namespace discord_rpc {
template<typename T> template<typename T>
void NumberToString(char *dest, T number) { void NumberToString(char *dest, T number) {
if (!number) { if (!number) {
*dest++ = '0'; *dest++ = '0';
*dest++ = 0; *dest++ = 0;
@@ -26,6 +27,7 @@ void NumberToString(char *dest, T number) {
*dest++ = temp[place]; *dest++ = temp[place];
} }
*dest = 0; *dest = 0;
} }
// it's ever so slightly faster to not have to strlen the key // it's ever so slightly faster to not have to strlen the key
@@ -62,24 +64,25 @@ struct WriteArray {
template<typename T> template<typename T>
void WriteOptionalString(JsonWriter &w, T &k, const char *value) { void WriteOptionalString(JsonWriter &w, T &k, const char *value) {
if (value && value[0]) { if (value && value[0]) {
w.Key(k, sizeof(T) - 1); w.Key(k, sizeof(T) - 1);
w.String(value); w.String(value);
} }
} }
static void JsonWriteNonce(JsonWriter &writer, int nonce) { static void JsonWriteNonce(JsonWriter &writer, const int nonce) {
WriteKey(writer, "nonce"); WriteKey(writer, "nonce");
char nonceBuffer[32]; char nonceBuffer[32];
NumberToString(nonceBuffer, nonce); NumberToString(nonceBuffer, nonce);
writer.String(nonceBuffer); writer.String(nonceBuffer);
} }
size_t JsonWriteRichPresenceObj(char *dest, size_t JsonWriteRichPresenceObj(char *dest, const size_t maxLen, const int nonce, const int pid, const DiscordRichPresence *presence) {
size_t maxLen,
int nonce,
int pid,
const DiscordRichPresence *presence) {
JsonWriter writer(dest, maxLen); JsonWriter writer(dest, maxLen);
{ {
@@ -168,6 +171,7 @@ size_t JsonWriteRichPresenceObj(char *dest,
} }
size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char *applicationId) { size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char *applicationId) {
JsonWriter writer(dest, maxLen); JsonWriter writer(dest, maxLen);
{ {
@@ -179,9 +183,11 @@ size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char
} }
return writer.Size(); return writer.Size();
} }
size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName) { size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName) {
JsonWriter writer(dest, maxLen); JsonWriter writer(dest, maxLen);
{ {
@@ -197,9 +203,11 @@ size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const cha
} }
return writer.Size(); return writer.Size();
} }
size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName) { size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName) {
JsonWriter writer(dest, maxLen); JsonWriter writer(dest, maxLen);
{ {
@@ -215,9 +223,11 @@ size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const c
} }
return writer.Size(); return writer.Size();
} }
size_t JsonWriteJoinReply(char *dest, size_t maxLen, const char *userId, int reply, int nonce) { size_t JsonWriteJoinReply(char *dest, size_t maxLen, const char *userId, const int reply, const int nonce) {
JsonWriter writer(dest, maxLen); JsonWriter writer(dest, maxLen);
{ {
@@ -243,6 +253,7 @@ size_t JsonWriteJoinReply(char *dest, size_t maxLen, const char *userId, int rep
} }
return writer.Size(); return writer.Size();
} }
} // namespace discord_rpc } // namespace discord_rpc

View File

@@ -25,11 +25,7 @@ size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char
// Commands // Commands
struct DiscordRichPresence; struct DiscordRichPresence;
size_t JsonWriteRichPresenceObj(char *dest, size_t JsonWriteRichPresenceObj(char *dest, const size_t maxLen, const int nonce, const int pid, const DiscordRichPresence *presence);
size_t maxLen,
int nonce,
int pid,
const DiscordRichPresence *presence);
size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName); size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName);
size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName); size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName);
@@ -149,35 +145,42 @@ class JsonDocument : public JsonDocumentBase {
using JsonValue = rapidjson::GenericValue<UTF8, PoolAllocator>; using JsonValue = rapidjson::GenericValue<UTF8, PoolAllocator>;
inline JsonValue *GetObjMember(JsonValue *obj, const char *name) { inline JsonValue *GetObjMember(JsonValue *obj, const char *name) {
if (obj) { if (obj) {
auto member = obj->FindMember(name); auto member = obj->FindMember(name);
if (member != obj->MemberEnd() && member->value.IsObject()) { if (member != obj->MemberEnd() && member->value.IsObject()) {
return &member->value; return &member->value;
} }
} }
return nullptr; return nullptr;
} }
inline int GetIntMember(JsonValue *obj, const char *name, int notFoundDefault = 0) { inline int GetIntMember(JsonValue *obj, const char *name, int notFoundDefault = 0) {
if (obj) { if (obj) {
auto member = obj->FindMember(name); auto member = obj->FindMember(name);
if (member != obj->MemberEnd() && member->value.IsInt()) { if (member != obj->MemberEnd() && member->value.IsInt()) {
return member->value.GetInt(); return member->value.GetInt();
} }
} }
return notFoundDefault; return notFoundDefault;
} }
inline const char *GetStrMember(JsonValue *obj, inline const char *GetStrMember(JsonValue *obj, const char *name, const char *notFoundDefault = nullptr) {
const char *name,
const char *notFoundDefault = nullptr) {
if (obj) { if (obj) {
auto member = obj->FindMember(name); auto member = obj->FindMember(name);
if (member != obj->MemberEnd() && member->value.IsString()) { if (member != obj->MemberEnd() && member->value.IsString()) {
return member->value.GetString(); return member->value.GetString();
} }
} }
return notFoundDefault; return notFoundDefault;
} }
} // namespace discord_rpc } // namespace discord_rpc

View File

@@ -1,41 +0,0 @@
set(DISCORD_RPC_SOURCES
../include/discord_rpc.h
../include/discord_register.h
discord_rpc.cpp
rpc_connection.h
rpc_connection.cpp
serialization.h
serialization.cpp
connection.h
backoff.h
msg_queue.h
)
if(UNIX)
list(APPEND DISCORD_RPC_SOURCES connection_unix.cpp)
if(APPLE)
list(APPEND DISCORD_RPC_SOURCES discord_register_osx.m)
add_definitions(-DDISCORD_OSX)
else()
list(APPEND DISCORD_RPC_SOURCES discord_register_linux.cpp)
add_definitions(-DDISCORD_LINUX)
endif()
endif()
if(WIN32)
list(APPEND DISCORD_RPC_SOURCES connection_win.cpp discord_register_win.cpp)
add_definitions(-DDISCORD_WINDOWS)
endif()
add_library(discord-rpc STATIC ${DISCORD_RPC_SOURCES})
if(APPLE)
target_link_libraries(discord-rpc PRIVATE "-framework AppKit")
endif()
if(WIN32)
target_link_libraries(discord-rpc PRIVATE psapi advapi32)
endif()
target_include_directories(discord-rpc SYSTEM PRIVATE ${RapidJSON_INCLUDE_DIRS})
target_include_directories(discord-rpc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include)

View File

@@ -1,80 +0,0 @@
#include <stdio.h>
#include <sys/stat.h>
#import <AppKit/AppKit.h>
#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
// to a file in the Discord config path, and when it is needed, Discord can try to load the file there, open
// the command therein (will pass to js's window.open, so requires a url-like thing)
// Note: will not work for sandboxed apps
NSString *home = NSHomeDirectory();
if (!home) {
return;
}
NSString *path = [[[[[[home stringByAppendingPathComponent:@"Library"]
stringByAppendingPathComponent:@"Application Support"]
stringByAppendingPathComponent:@"discord"]
stringByAppendingPathComponent:@"games"]
stringByAppendingPathComponent:[NSString stringWithUTF8String:applicationId]]
stringByAppendingPathExtension:@"json"];
[[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
NSString *jsonBuffer = [NSString stringWithFormat:@"{\"command\": \"%s\"}", command];
[jsonBuffer writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil];
}
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];
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;
}
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);
}
}
void Discord_Register(const char *applicationId, const char *command)
{
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, 256, "steam://rungameid/%s", steamId);
Discord_Register(applicationId, command);
}

View File

@@ -1494,7 +1494,7 @@ endif()
if(HAVE_DISCORD_RPC) if(HAVE_DISCORD_RPC)
add_subdirectory(3rdparty/discord-rpc) add_subdirectory(3rdparty/discord-rpc)
target_include_directories(strawberry_lib PUBLIC 3rdparty/discord-rpc/include) target_include_directories(strawberry_lib PUBLIC 3rdparty/discord-rpc)
endif() endif()
if(HAVE_TRANSLATIONS) if(HAVE_TRANSLATIONS)

View File

@@ -52,7 +52,7 @@ RichPresence::RichPresence(const SharedPtr<Player> player,
send_presence_timestamp_(0), send_presence_timestamp_(0),
enabled_(false) { enabled_(false) {
Discord_Initialize(kDiscordApplicationId, nullptr, 1, nullptr); Discord_Initialize(kDiscordApplicationId, nullptr, 1);
QObject::connect(&*player_->engine(), &EngineBase::StateChanged, this, &RichPresence::EngineStateChanged); QObject::connect(&*player_->engine(), &EngineBase::StateChanged, this, &RichPresence::EngineStateChanged);
QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentSongChanged, this, &RichPresence::CurrentSongChanged); QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentSongChanged, this, &RichPresence::CurrentSongChanged);