diff --git a/build.py b/build.py old mode 100644 new mode 100755 index 358985b..6d984a9 --- a/build.py +++ b/build.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import click import os import subprocess @@ -45,7 +47,7 @@ def build_lib(build_name, generator, options): def create_archive(): - archive_file_path = os.path.join(SCRIPT_PATH, 'builds', 'discord-rpc.zip') + archive_file_path = os.path.join(SCRIPT_PATH, 'builds', 'discord-rpc-%s.zip' % sys.platform) archive_file = zipfile.ZipFile(archive_file_path, 'w', zipfile.ZIP_DEFLATED) archive_src_base_path = os.path.join(SCRIPT_PATH, 'builds', 'install') archive_dst_base_path = 'discord-rpc' @@ -64,6 +66,8 @@ def main(clean): if clean: shutil.rmtree('builds', ignore_errors=True) + mkdir_p('builds') + if sys.platform.startswith('win'): generator32 = 'Visual Studio 14 2015' generator64 = 'Visual Studio 14 2015 Win64' @@ -79,9 +83,12 @@ def main(clean): shutil.copy(src_dll, dst_dll) dst_dll = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc', 'Binaries', 'ThirdParty', 'discordrpcLibrary', 'Win64', 'discord-rpc.dll') shutil.copy(src_dll, dst_dll) + elif sys.platform == 'darwin': + build_lib('osx-static', None, {}) + build_lib('osx-dynamic', None, {'BUILD_DYNAMIC_LIB': True}) create_archive() if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file + sys.exit(main()) diff --git a/documentation/hard-mode.md b/documentation/hard-mode.md index 50dcfa0..cb688d1 100644 --- a/documentation/hard-mode.md +++ b/documentation/hard-mode.md @@ -10,7 +10,7 @@ By committing to an RPC-only integration, you decide to forego the work our libr ## Application Protocol Registration -One thing that cannot be explicitly done over RPC is registering an application protocol for your game. If you choose to do an RPC-only implementation, you will have to register your application protocol yourself in the format of `discord-[your_app_id]://`. You can use `Discord_Register()` from `src/discord-register.cpp` as a good example of how to properly register an application protocol for use with Discord. +One thing that cannot be explicitly done over RPC is registering an application protocol for your game. If you choose to do an RPC-only implementation, you will have to register your application protocol yourself in the format of `discord-[your_app_id]://`. You can use `Discord_Register()` as a good(?) example of how to properly register an application protocol for use with Discord. For OSX and Linux it is probably simpler to handle the protocol registration as part of your install/packaging. ## New RPC Command diff --git a/examples/send-presence/CMakeLists.txt b/examples/send-presence/CMakeLists.txt index 68ea181..8a67d47 100644 --- a/examples/send-presence/CMakeLists.txt +++ b/examples/send-presence/CMakeLists.txt @@ -1,5 +1,13 @@ include_directories(${PROJECT_SOURCE_DIR}/include) -add_executable(send-presence send-presence.c) +add_executable( + send-presence + MACOSX_BUNDLE + send-presence.c +) +set_target_properties(send-presence PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "Send Presence" + MACOSX_BUNDLE_GUI_IDENTIFIER "com.discordapp.examples.send-presence" +) target_link_libraries(send-presence discord-rpc) install( @@ -7,4 +15,7 @@ install( RUNTIME DESTINATION "bin" CONFIGURATIONS Release + BUNDLE + DESTINATION "bin" + CONFIGURATIONS Release ) \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f7fc7cb..d4621d1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,8 +6,7 @@ option(BUILD_DYNAMIC_LIB "Build library as a DLL" OFF) set(BASE_RPC_SRC ${PROJECT_SOURCE_DIR}/include/discord-rpc.h discord-rpc.cpp - discord-register.h - discord-register.cpp + discord_register.h rpc_connection.h rpc_connection.cpp serialization.h @@ -18,20 +17,39 @@ set(BASE_RPC_SRC if (${BUILD_DYNAMIC_LIB}) set(RPC_LIBRARY_TYPE SHARED) - set(BASE_RPC_SRC ${BASE_RPC_SRC} dllmain.cpp) + if(WIN32) + set(BASE_RPC_SRC ${BASE_RPC_SRC} dllmain.cpp) + endif(WIN32) else(${BUILD_DYNAMIC_LIB}) set(RPC_LIBRARY_TYPE STATIC) endif(${BUILD_DYNAMIC_LIB}) if(WIN32) - add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC} connection_win.cpp) + add_definitions(-DDISCORD_WINDOWS) + set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_win.cpp discord_register_win.cpp) + add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC}) target_compile_options(discord-rpc PRIVATE /W4) endif(WIN32) if(UNIX) - add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC} connection_unix.cpp) + set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_unix.cpp) + + if (APPLE) + add_definitions(-DDISCORD_OSX) + set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_osx.m) + else (APPLE) + add_definitions(-DDISCORD_LINUX) + set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_linux.cpp) + endif(APPLE) + + add_library(discord-rpc ${RPC_LIBRARY_TYPE} ${BASE_RPC_SRC}) target_link_libraries(discord-rpc PUBLIC pthread) - target_compile_options(discord-rpc PRIVATE -g -Wall -std=c++14) + target_compile_options(discord-rpc PRIVATE -g -Wall) + target_compile_options(discord-rpc PRIVATE $<$:-std=c++14>) + + if (APPLE) + target_link_libraries(discord-rpc PRIVATE "-framework AppKit") + endif (APPLE) endif(UNIX) target_include_directories(discord-rpc PRIVATE ${RAPIDJSON}/include) diff --git a/src/discord-rpc.cpp b/src/discord-rpc.cpp index 86b66c5..117e86d 100644 --- a/src/discord-rpc.cpp +++ b/src/discord-rpc.cpp @@ -1,7 +1,7 @@ #include "discord-rpc.h" #include "backoff.h" -#include "discord-register.h" +#include "discord_register.h" #include "rpc_connection.h" #include "serialization.h" @@ -93,7 +93,7 @@ static void SendQueueCommitMessage() SendQueuePendingSends++; } -extern "C" void Discord_UpdateConnection() +DISCORD_EXPORT void Discord_UpdateConnection() { if (!Connection) { return; @@ -210,7 +210,7 @@ bool RegisterForEvent(const char* evtName) return false; } -extern "C" void Discord_Initialize(const char* applicationId, +DISCORD_EXPORT void Discord_Initialize(const char* applicationId, DiscordEventHandlers* handlers, int autoRegister, const char* optionalSteamId) @@ -263,7 +263,7 @@ extern "C" void Discord_Initialize(const char* applicationId, #endif } -extern "C" void Discord_Shutdown() +DISCORD_EXPORT void Discord_Shutdown() { if (!Connection) { return; @@ -281,7 +281,7 @@ extern "C" void Discord_Shutdown() RpcConnection::Destroy(Connection); } -extern "C" void Discord_UpdatePresence(const DiscordRichPresence* presence) +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence) { PresenceMutex.lock(); QueuedPresence.length = JsonWriteRichPresenceObj( @@ -290,7 +290,7 @@ extern "C" void Discord_UpdatePresence(const DiscordRichPresence* presence) SignalIOActivity(); } -extern "C" void Discord_RunCallbacks() +DISCORD_EXPORT void Discord_RunCallbacks() { // 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 diff --git a/src/discord-register.h b/src/discord_register.h similarity index 70% rename from src/discord-register.h rename to src/discord_register.h index 8df2105..876b307 100644 --- a/src/discord-register.h +++ b/src/discord_register.h @@ -1,4 +1,12 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + void Discord_Register(const char* applicationId, const char* command); void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/discord_register_linux.cpp b/src/discord_register_linux.cpp new file mode 100644 index 0000000..8bf08eb --- /dev/null +++ b/src/discord_register_linux.cpp @@ -0,0 +1,96 @@ +#include "discord-rpc.h" +#include + +#include +#include +#include +#include +#include +#include + +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-:// +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. + + const char* home = getenv("HOME"); + if (!home) { + return; + } + + char exePath[1024]; + if (!command || !command[0]) { + if (readlink("/proc/self/exe", exePath, sizeof(exePath)) <= 0) { + return; + } + command = exePath; + } + + const char* destopFileFormat = "[Desktop Entry]\n" + "Name=Game %s\n" + "Exec=%s %%u\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); + system(xdgMimeCommand); +} + +extern "C" void Discord_RegisterSteamGame(const char* applicationId, const char* steamId) +{ + char command[256]; + sprintf(command, "xdg-open steam://run/%s", steamId); + Discord_Register(applicationId, command); +} diff --git a/src/discord_register_osx.m b/src/discord_register_osx.m new file mode 100644 index 0000000..1951b9a --- /dev/null +++ b/src/discord_register_osx.m @@ -0,0 +1,95 @@ +#include +#include + +#import + +static bool Mkdir(const char* path) +{ + int result = mkdir(path, 0755); + if (result == 0) { + return true; + } + if (errno == EEXIST) { + return true; + } + return false; +} + +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) + + const char* home = getenv("HOME"); + if (!home) { + return; + } + + char path[2048]; + sprintf(path, "%s/Library/Application Support/discord", home); + Mkdir(path); + strcat(path, "/games"); + Mkdir(path); + strcat(path, "/"); + strcat(path, applicationId); + strcat(path, ".json"); + + FILE* f = fopen(path, "w"); + if (f) { + char jsonBuffer[2048]; + int len = snprintf(jsonBuffer, sizeof(jsonBuffer), "{\"command\": \"%s\"}", command); + fwrite(jsonBuffer, len, 1, f); + fclose(f); + } +} + +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 + void* pool = [[NSAutoreleasePool alloc] init]; + RegisterURL(applicationId); + [(id)pool drain]; + } +} + +void Discord_RegisterSteamGame(const char* applicationId, const char* steamId) +{ + char command[256]; + sprintf(command, "steam://run/%s", steamId); + Discord_Register(applicationId, command); +} diff --git a/src/discord-register.cpp b/src/discord_register_win.cpp similarity index 94% rename from src/discord-register.cpp rename to src/discord_register_win.cpp index 05a7f6b..8ac5b42 100644 --- a/src/discord-register.cpp +++ b/src/discord_register_win.cpp @@ -1,7 +1,6 @@ #include "discord-rpc.h" #include -#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define NOMCX #define NOSERVICE @@ -10,9 +9,7 @@ #include #include #pragma comment(lib, "Psapi.lib") -#endif -#ifdef _WIN32 void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command) { // https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx @@ -76,12 +73,9 @@ void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command) } RegCloseKey(key); } -#endif -void Discord_Register(const char* applicationId, const char* command) +extern "C" void Discord_Register(const char* applicationId, const char* command) { -#ifdef _WIN32 - wchar_t appId[32]; MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); @@ -94,12 +88,10 @@ void Discord_Register(const char* applicationId, const char* command) } Discord_RegisterW(appId, wcommand); -#endif } -void Discord_RegisterSteamGame(const char* applicationId, const char* steamId) +extern "C" void Discord_RegisterSteamGame(const char* applicationId, const char* steamId) { -#ifdef _WIN32 wchar_t appId[32]; MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); @@ -133,5 +125,4 @@ void Discord_RegisterSteamGame(const char* applicationId, const char* steamId) StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://run/%s", steamPath, wSteamId); Discord_RegisterW(appId, command); -#endif }