Compare commits

..

No commits in common. "master" and "v3.2.0" have entirely different histories.

22 changed files with 224 additions and 411 deletions

View File

@ -43,5 +43,5 @@ before_install:
script: script:
- mkdir build - mkdir build
- cd build - cd build
- cmake -DCLANG_FORMAT_SUFFIX=$CLANG_FORMAT_SUFFIX -DWARNINGS_AS_ERRORS=On --config Release .. - cmake -DCLANG_FORMAT_SUFFIX=$CLANG_FORMAT_SUFFIX --config Release ..
- cmake --build . -- -j2 - cmake --build . -- -j2

107
README.md
View File

@ -1,11 +1,5 @@
# Discord RPC # Discord RPC
## Deprecation Notice
This library has been deprecated in favor of Discord's GameSDK. [Learn more here](https://discordapp.com/developers/docs/game-sdk/sdk-starter-guide)
---
This is a library for interfacing your game with a locally running Discord desktop client. It's known to work on Windows, macOS, and Linux. You can use the lib directly if you like, or use it as a guide to writing your own if it doesn't suit your game as is. PRs/feedback welcome if you have an improvement everyone might want, or can describe how this doesn't meet your needs. This is a library for interfacing your game with a locally running Discord desktop client. It's known to work on Windows, macOS, and Linux. You can use the lib directly if you like, or use it as a guide to writing your own if it doesn't suit your game as is. PRs/feedback welcome if you have an improvement everyone might want, or can describe how this doesn't meet your needs.
Included here are some quick demos that implement the very minimal subset to show current status, and Included here are some quick demos that implement the very minimal subset to show current status, and
@ -21,64 +15,6 @@ Zeroith, you should be set up to build things because you are a game developer,
First, head on over to the [Discord developers site](https://discordapp.com/developers/applications/me) and make yourself an app. Keep track of `Client ID` -- you'll need it here to pass to the init function. First, head on over to the [Discord developers site](https://discordapp.com/developers/applications/me) and make yourself an app. Keep track of `Client ID` -- you'll need it here to pass to the init function.
### Unreal Engine 4 Setup
To use the Rich Presense plugin with Unreal Engine Projects:
1. Download the latest [release](https://github.com/discordapp/discord-rpc/releases) for each operating system you are targeting and the zipped source code
2. In the source code zip, copy the UE plugin—`examples/unrealstatus/Plugins/discordrpc`—to your project's plugin directory
3. At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create an `Include` folder and copy `discord_rpc.h` and `discord_register.h` to it from the zip
4. Follow the steps below for each OS
5. Build your UE4 project
6. Launch the editor, and enable the Discord plugin.
#### Windows
- At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create a `Win64` folder
- Copy `lib/discord-rpc.lib` and `bin/discord-rpc.dll` from `[RELEASE_ZIP]/win64-dynamic` to the `Win64` folder
#### Mac
- At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create a `Mac` folder
- Copy `libdiscord-rpc.dylib` from `[RELEASE_ZIP]/osx-dynamic/lib` to the `Mac` folder
#### Linux
- At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create a `Linux` folder
- Inside, create another folder `x86_64-unknown-linux-gnu`
- Copy `libdiscord-rpc.so` from `[RELEASE_ZIP]/linux-dynamic/lib` to `Linux/x86_64-unknown-linux-gnu`
### Unity Setup
If you're a Unity developer looking to integrate Rich Presence into your game, follow this simple guide to get started towards success:
1. Download the DLLs for any platform that you need from [our releases](https://github.com/discordapp/discord-rpc/releases)
2. In your Unity project, create a `Plugins` folder inside your `Assets` folder if you don't already have one
3. Copy the file `DiscordRpc.cs` from [here](https://github.com/discordapp/discord-rpc/blob/master/examples/button-clicker/Assets/DiscordRpc.cs) into your `Assets` folder. This is basically your header file for the SDK
We've got our `Plugins` folder ready, so let's get platform-specific!
#### Windows
4. Create `x86` and `x86_64` folders inside `Assets/Plugins/`
5. Copy `discord-rpc-win/win64-dynamic/bin/discord-rpc.dll` to `Assets/Plugins/x86_64/`
6. Copy `discord-rpc-win/win32-dynamic/bin/discord-rpc.dll` to `Assets/Plugins/x86/`
7. Click on both DLLs and make sure they are targetting the correct architectures in the Unity editor properties pane
8. Done!
#### MacOS
4. Copy `discord-rpc-osx/osx-dynamic/lib/libdiscord-rpc.dylib` to `Assets/Plugins/`
5. Rename `libdiscord-rpc.dylib` to `discord-rpc.bundle`
6. Done!
#### Linux
4. Copy `discord-rpc-linux/linux-dynamic-lib/libdiscord-rpc.so` to `Assets/Plugins/`
5. Done!
You're ready to roll! For code examples on how to interact with the SDK using the `DiscordRpc.cs` header file, check out [our example](https://github.com/discordapp/discord-rpc/blob/master/examples/button-clicker/Assets/DiscordController.cs)
### From package ### From package
Download a release package for your platform(s) -- they have subdirs with various prebuilt options, select the one you need add `/include` to your compile includes, `/lib` to your linker paths, and link with `discord-rpc`. For the dynamically linked builds, you'll need to ship the associated file along with your game. Download a release package for your platform(s) -- they have subdirs with various prebuilt options, select the one you need add `/include` to your compile includes, `/lib` to your linker paths, and link with `discord-rpc`. For the dynamically linked builds, you'll need to ship the associated file along with your game.
@ -90,7 +26,6 @@ First-eth, you'll want `CMake`. There's a few different ways to install it on yo
To make sure it's installed correctly, type `cmake --version` into your flavor of terminal/cmd. If you get a response with a version number, you're good to go! To make sure it's installed correctly, type `cmake --version` into your flavor of terminal/cmd. If you get a response with a version number, you're good to go!
There's a [CMake](https://cmake.org/download/) file that should be able to generate the lib for you; Sometimes I use it like this: There's a [CMake](https://cmake.org/download/) file that should be able to generate the lib for you; Sometimes I use it like this:
```sh ```sh
cd <path to discord-rpc> cd <path to discord-rpc>
mkdir build mkdir build
@ -98,29 +33,27 @@ There's a [CMake](https://cmake.org/download/) file that should be able to gener
cmake .. -DCMAKE_INSTALL_PREFIX=<path to install discord-rpc to> cmake .. -DCMAKE_INSTALL_PREFIX=<path to install discord-rpc to>
cmake --build . --config Release --target install cmake --build . --config Release --target install
``` ```
There is a wrapper build script `build.py` that runs `cmake` with a few different options. There is a wrapper build script `build.py` that runs `cmake` with a few different options.
Usually, I run `build.py` to get things started, then use the generated project files as I work on things. It does depend on `click` library, so do a quick `pip install click` to make sure you have it if you want to run `build.py`. Usually, I run `build.py` to get things started, then use the generated project files as I work on things. It does depend on `click` library, so do a quick `pip install click` to make sure you have it if you want to run `build.py`.
There are some CMake options you might care about: There are some CMake options you might care about:
| flag | default | does | | flag | default | does |
| ---------------------------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | |------|---------|------|
| `ENABLE_IO_THREAD` | `ON` | When enabled, we start up a thread to do io processing, if disabled you should call `Discord_UpdateConnection` yourself. | | `ENABLE_IO_THREAD` | `ON` | When enabled, we start up a thread to do io processing, if disabled you should call `Discord_UpdateConnection` yourself.
| `USE_STATIC_CRT` | `OFF` | (Windows) Enable to statically link the CRT, avoiding requiring users install the redistributable package. (The prebuilt binaries enable this option) | | `USE_STATIC_CRT` | `OFF` | (Windows) Enable to statically link the CRT, avoiding requiring users install the redistributable package. (The prebuilt binaries enable this option)
| [`BUILD_SHARED_LIBS`](https://cmake.org/cmake/help/v3.7/variable/BUILD_SHARED_LIBS.html) | `OFF` | Build library as a DLL | | [`BUILD_SHARED_LIBS`](https://cmake.org/cmake/help/v3.7/variable/BUILD_SHARED_LIBS.html) | `OFF` | Build library as a DLL
| `WARNINGS_AS_ERRORS` | `OFF` | When enabled, compiles with `-Werror` (on \*nix platforms). |
## Continuous Builds ## Continuous Builds
Why do we have three of these? Three times the fun! Why do we have three of these? Three times the fun!
| CI | badge | | CI | badge |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | |----|-------|
| TravisCI | [![Build status](https://travis-ci.org/discordapp/discord-rpc.svg?branch=master)](https://travis-ci.org/discordapp/discord-rpc) | | TravisCI | [![Build status](https://travis-ci.org/discordapp/discord-rpc.svg?branch=master)](https://travis-ci.org/discordapp/discord-rpc)
| AppVeyor | [![Build status](https://ci.appveyor.com/api/projects/status/qvkoc0w1c4f4b8tj?svg=true)](https://ci.appveyor.com/project/crmarsh/discord-rpc) | | AppVeyor | [![Build status](https://ci.appveyor.com/api/projects/status/qvkoc0w1c4f4b8tj?svg=true)](https://ci.appveyor.com/project/crmarsh/discord-rpc)
| Buildkite (internal) | [![Build status](https://badge.buildkite.com/e103d79d247f6776605a15246352a04b8fd83d69211b836111.svg)](https://buildkite.com/discord/discord-rpc) | | Buildkite (internal) | [![Build status](https://badge.buildkite.com/e103d79d247f6776605a15246352a04b8fd83d69211b836111.svg)](https://buildkite.com/discord/discord-rpc)
## Sample: send-presence ## Sample: send-presence
@ -144,15 +77,11 @@ Below is a table of unofficial, community-developed wrappers for and implementat
###### Rich Presence Wrappers and Implementations ###### Rich Presence Wrappers and Implementations
| Name | Language | | Name | Language |
| ------------------------------------------------------------------------- | --------------------------------- | |------|----------|
| [Discord RPC C#](https://github.com/Lachee/discord-rpc-csharp) | C# | | [discord-rpc.jar](https://github.com/Vatuu/discord-rpc "Discord-RPC.jar") | Java |
| [Discord RPC D](https://github.com/voidblaster/discord-rpc-d) | [D](https://dlang.org/) | | [java-discord-rpc](https://github.com/MinnDevelopment/java-discord-rpc) | Java |
| [discord-rpc.jar](https://github.com/Vatuu/discord-rpc 'Discord-RPC.jar') | Java | | [Discord-IPC](https://github.com/jagrosh/DiscordIPC) | Java |
| [java-discord-rpc](https://github.com/MinnDevelopment/java-discord-rpc) | Java | | [Discord Rich Presence](https://npmjs.org/discord-rich-presence) | JavaScript |
| [Discord-IPC](https://github.com/jagrosh/DiscordIPC) | Java | | [drpc4k](https://github.com/Bluexin/drpc4k) | [Kotlin](https://kotlinlang.org/) |
| [Discord Rich Presence](https://npmjs.org/discord-rich-presence) | JavaScript | | [SwordRPC](https://github.com/Azoy/SwordRPC) | [Swift](https://swift.org) |
| [drpc4k](https://github.com/Bluexin/drpc4k) | [Kotlin](https://kotlinlang.org/) |
| [lua-discordRPC](https://github.com/pfirsich/lua-discordRPC) | LuaJIT (FFI) |
| [pypresence](https://github.com/qwertyquerty/pypresence) | [Python](https://python.org/) |
| [SwordRPC](https://github.com/Azoy/SwordRPC) | [Swift](https://swift.org) |

View File

@ -30,7 +30,7 @@ INSTALL_ROOT = os.path.join(SCRIPT_PATH, 'builds', 'install')
def get_signtool(): def get_signtool():
""" get path to code signing tool """ """ get path to code signing tool """
if PLATFORM == 'win': if PLATFORM == 'win':
sdk_dir = 'c:\\Program Files (x86)\\Windows Kits\\10' # os.environ['WindowsSdkDir'] sdk_dir = os.environ['WindowsSdkDir']
return os.path.join(sdk_dir, 'bin', 'x86', 'signtool.exe') return os.path.join(sdk_dir, 'bin', 'x86', 'signtool.exe')
elif PLATFORM == 'osx': elif PLATFORM == 'osx':
return '/usr/bin/codesign' return '/usr/bin/codesign'
@ -70,7 +70,14 @@ def cli(ctx, clean):
@click.pass_context @click.pass_context
def unity(ctx): def unity(ctx):
""" build just dynamic libs for use in unity project """ """ build just dynamic libs for use in unity project """
ctx.invoke(libs, clean=False, static=False, shared=True, skip_formatter=True, just_release=True) ctx.invoke(
libs,
clean=False,
static=False,
shared=True,
skip_formatter=True,
just_release=True
)
BUILDS = [] BUILDS = []
click.echo('--- Copying libs and header into unity example') click.echo('--- Copying libs and header into unity example')
@ -90,8 +97,7 @@ def unity(ctx):
LIBRARY_NAME = 'discord-rpc.bundle' LIBRARY_NAME = 'discord-rpc.bundle'
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'osx-dynamic', 'src') BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'osx-dynamic', 'src')
UNITY_DLL_PATH = UNITY_PROJECT_PATH UNITY_DLL_PATH = UNITY_PROJECT_PATH
os.rename( os.rename(os.path.join(BUILD_BASE_PATH, 'libdiscord-rpc.dylib'), os.path.join(BUILD_BASE_PATH, 'discord-rpc.bundle'))
os.path.join(BUILD_BASE_PATH, 'libdiscord-rpc.dylib'), os.path.join(BUILD_BASE_PATH, 'discord-rpc.bundle'))
BUILDS.append({BUILD_BASE_PATH: UNITY_DLL_PATH}) BUILDS.append({BUILD_BASE_PATH: UNITY_DLL_PATH})
@ -116,7 +122,14 @@ def unity(ctx):
@click.pass_context @click.pass_context
def unreal(ctx): def unreal(ctx):
""" build libs and copy them into the unreal project """ """ build libs and copy them into the unreal project """
ctx.invoke(libs, clean=False, static=False, shared=True, skip_formatter=True, just_release=True) ctx.invoke(
libs,
clean=False,
static=False,
shared=True,
skip_formatter=True,
just_release=True
)
BUILDS = [] BUILDS = []
click.echo('--- Copying libs and header into unreal example') click.echo('--- Copying libs and header into unreal example')
@ -165,7 +178,11 @@ def build_lib(build_name, generator, options, just_release):
mkdir_p(build_path) mkdir_p(build_path)
mkdir_p(install_path) mkdir_p(install_path)
with cd(build_path): with cd(build_path):
initial_cmake = ['cmake', SCRIPT_PATH, '-DCMAKE_INSTALL_PREFIX=%s' % os.path.join('..', 'install', build_name)] initial_cmake = [
'cmake',
SCRIPT_PATH,
'-DCMAKE_INSTALL_PREFIX=%s' % os.path.join('..', 'install', build_name)
]
if generator: if generator:
initial_cmake.extend(['-G', generator]) initial_cmake.extend(['-G', generator])
for key in options: for key in options:
@ -207,28 +224,22 @@ def sign():
sign_command_base = [ sign_command_base = [
tool, tool,
'sign', 'sign',
'/n', '/n', 'Discord Inc.',
'Discord Inc.',
'/a', '/a',
'/tr', '/tr', 'http://timestamp.digicert.com/rfc3161',
'http://timestamp.digicert.com/rfc3161',
'/as', '/as',
'/td', '/td', 'sha256',
'sha256', '/fd', 'sha256',
'/fd',
'sha256',
] ]
elif PLATFORM == 'osx': elif PLATFORM == 'osx':
signable_extensions.add('.dylib') signable_extensions.add('.dylib')
sign_command_base = [ sign_command_base = [
tool, tool,
'--keychain', '--keychain', os.path.expanduser('~/Library/Keychains/login.keychain'),
os.path.expanduser('~/Library/Keychains/login.keychain'),
'-vvvv', '-vvvv',
'--deep', '--deep',
'--force', '--force',
'--sign', '--sign', 'Developer ID Application: Hammer & Chisel Inc. (53Q6R32WPB)',
'Developer ID Application: Hammer & Chisel Inc. (53Q6R32WPB)',
] ]
else: else:
click.secho('Not signing things on this platform yet', fg='red') click.secho('Not signing things on this platform yet', fg='red')
@ -275,8 +286,6 @@ def libs(clean, static, shared, skip_formatter, just_release):
if IS_BUILD_MACHINE: if IS_BUILD_MACHINE:
just_release = True just_release = True
static_options['WARNINGS_AS_ERRORS'] = True
dynamic_options['WARNINGS_AS_ERRORS'] = True
if PLATFORM == 'win': if PLATFORM == 'win':
generator32 = 'Visual Studio 14 2015' generator32 = 'Visual Studio 14 2015'

View File

@ -7,15 +7,16 @@ public class DiscordJoinEvent : UnityEngine.Events.UnityEvent<string> { }
public class DiscordSpectateEvent : UnityEngine.Events.UnityEvent<string> { } public class DiscordSpectateEvent : UnityEngine.Events.UnityEvent<string> { }
[System.Serializable] [System.Serializable]
public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent<DiscordRpc.DiscordUser> { } public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent<DiscordRpc.JoinRequest> { }
public class DiscordController : MonoBehaviour public class DiscordController : MonoBehaviour
{ {
public DiscordRpc.RichPresence presence = new DiscordRpc.RichPresence(); public DiscordRpc.RichPresence presence = new DiscordRpc.RichPresence();
public string applicationId; public string applicationId;
public string optionalSteamId; public string optionalSteamId;
public int callbackCalls;
public int clickCounter; public int clickCounter;
public DiscordRpc.DiscordUser joinRequest; public DiscordRpc.JoinRequest joinRequest;
public UnityEngine.Events.UnityEvent onConnect; public UnityEngine.Events.UnityEvent onConnect;
public UnityEngine.Events.UnityEvent onDisconnect; public UnityEngine.Events.UnityEvent onDisconnect;
public UnityEngine.Events.UnityEvent hasResponded; public UnityEngine.Events.UnityEvent hasResponded;
@ -31,11 +32,6 @@ public class DiscordController : MonoBehaviour
clickCounter++; clickCounter++;
presence.details = string.Format("Button clicked {0} times", clickCounter); presence.details = string.Format("Button clicked {0} times", clickCounter);
presence.joinSecret = "aSecret";
presence.partyId = "aPartyId";
presence.partySize = 1;
presence.partyMax = 3;
presence.partyPrivacy = DiscordRpc.PartyPrivacy.Public;
DiscordRpc.UpdatePresence(presence); DiscordRpc.UpdatePresence(presence);
} }
@ -54,37 +50,43 @@ public class DiscordController : MonoBehaviour
hasResponded.Invoke(); hasResponded.Invoke();
} }
public void ReadyCallback(ref DiscordRpc.DiscordUser connectedUser) public void ReadyCallback()
{ {
Debug.Log(string.Format("Discord: connected to {0}#{1}: {2}", connectedUser.username, connectedUser.discriminator, connectedUser.userId)); ++callbackCalls;
Debug.Log("Discord: ready");
onConnect.Invoke(); onConnect.Invoke();
} }
public void DisconnectedCallback(int errorCode, string message) public void DisconnectedCallback(int errorCode, string message)
{ {
++callbackCalls;
Debug.Log(string.Format("Discord: disconnect {0}: {1}", errorCode, message)); Debug.Log(string.Format("Discord: disconnect {0}: {1}", errorCode, message));
onDisconnect.Invoke(); onDisconnect.Invoke();
} }
public void ErrorCallback(int errorCode, string message) public void ErrorCallback(int errorCode, string message)
{ {
++callbackCalls;
Debug.Log(string.Format("Discord: error {0}: {1}", errorCode, message)); Debug.Log(string.Format("Discord: error {0}: {1}", errorCode, message));
} }
public void JoinCallback(string secret) public void JoinCallback(string secret)
{ {
++callbackCalls;
Debug.Log(string.Format("Discord: join ({0})", secret)); Debug.Log(string.Format("Discord: join ({0})", secret));
onJoin.Invoke(secret); onJoin.Invoke(secret);
} }
public void SpectateCallback(string secret) public void SpectateCallback(string secret)
{ {
++callbackCalls;
Debug.Log(string.Format("Discord: spectate ({0})", secret)); Debug.Log(string.Format("Discord: spectate ({0})", secret));
onSpectate.Invoke(secret); onSpectate.Invoke(secret);
} }
public void RequestCallback(ref DiscordRpc.DiscordUser request) public void RequestCallback(ref DiscordRpc.JoinRequest request)
{ {
++callbackCalls;
Debug.Log(string.Format("Discord: join request {0}#{1}: {2}", request.username, request.discriminator, request.userId)); Debug.Log(string.Format("Discord: join request {0}#{1}: {2}", request.username, request.discriminator, request.userId));
joinRequest = request; joinRequest = request;
onJoinRequest.Invoke(request); onJoinRequest.Invoke(request);
@ -102,8 +104,10 @@ public class DiscordController : MonoBehaviour
void OnEnable() void OnEnable()
{ {
Debug.Log("Discord: init"); Debug.Log("Discord: init");
callbackCalls = 0;
handlers = new DiscordRpc.EventHandlers(); handlers = new DiscordRpc.EventHandlers();
handlers.readyCallback += ReadyCallback; handlers.readyCallback = ReadyCallback;
handlers.disconnectedCallback += DisconnectedCallback; handlers.disconnectedCallback += DisconnectedCallback;
handlers.errorCallback += ErrorCallback; handlers.errorCallback += ErrorCallback;
handlers.joinCallback += JoinCallback; handlers.joinCallback += JoinCallback;

View File

@ -2,44 +2,35 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using AOT;
public class DiscordRpc public class DiscordRpc
{ {
[MonoPInvokeCallback(typeof(OnReadyInfo))] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public static void ReadyCallback(ref DiscordUser connectedUser) { Callbacks.readyCallback(ref connectedUser); } public delegate void ReadyCallback();
public delegate void OnReadyInfo(ref DiscordUser connectedUser);
[MonoPInvokeCallback(typeof(OnDisconnectedInfo))] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public static void DisconnectedCallback(int errorCode, string message) { Callbacks.disconnectedCallback(errorCode, message); } public delegate void DisconnectedCallback(int errorCode, string message);
public delegate void OnDisconnectedInfo(int errorCode, string message);
[MonoPInvokeCallback(typeof(OnErrorInfo))] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public static void ErrorCallback(int errorCode, string message) { Callbacks.errorCallback(errorCode, message); } public delegate void ErrorCallback(int errorCode, string message);
public delegate void OnErrorInfo(int errorCode, string message);
[MonoPInvokeCallback(typeof(OnJoinInfo))] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public static void JoinCallback(string secret) { Callbacks.joinCallback(secret); } public delegate void JoinCallback(string secret);
public delegate void OnJoinInfo(string secret);
[MonoPInvokeCallback(typeof(OnSpectateInfo))] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public static void SpectateCallback(string secret) { Callbacks.spectateCallback(secret); } public delegate void SpectateCallback(string secret);
public delegate void OnSpectateInfo(string secret);
[MonoPInvokeCallback(typeof(OnRequestInfo))] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public static void RequestCallback(ref DiscordUser request) { Callbacks.requestCallback(ref request); } public delegate void RequestCallback(ref JoinRequest request);
public delegate void OnRequestInfo(ref DiscordUser request);
static EventHandlers Callbacks { get; set; }
public struct EventHandlers public struct EventHandlers
{ {
public OnReadyInfo readyCallback; public ReadyCallback readyCallback;
public OnDisconnectedInfo disconnectedCallback; public DisconnectedCallback disconnectedCallback;
public OnErrorInfo errorCallback; public ErrorCallback errorCallback;
public OnJoinInfo joinCallback; public JoinCallback joinCallback;
public OnSpectateInfo spectateCallback; public SpectateCallback spectateCallback;
public OnRequestInfo requestCallback; public RequestCallback requestCallback;
} }
[Serializable, StructLayout(LayoutKind.Sequential)] [Serializable, StructLayout(LayoutKind.Sequential)]
@ -56,7 +47,6 @@ public class DiscordRpc
public IntPtr partyId; /* max 128 bytes */ public IntPtr partyId; /* max 128 bytes */
public int partySize; public int partySize;
public int partyMax; public int partyMax;
public int partyPrivacy;
public IntPtr matchSecret; /* max 128 bytes */ public IntPtr matchSecret; /* max 128 bytes */
public IntPtr joinSecret; /* max 128 bytes */ public IntPtr joinSecret; /* max 128 bytes */
public IntPtr spectateSecret; /* max 128 bytes */ public IntPtr spectateSecret; /* max 128 bytes */
@ -64,7 +54,7 @@ public class DiscordRpc
} }
[Serializable] [Serializable]
public struct DiscordUser public struct JoinRequest
{ {
public string userId; public string userId;
public string username; public string username;
@ -79,29 +69,8 @@ public class DiscordRpc
Ignore = 2 Ignore = 2
} }
public enum PartyPrivacy
{
Private = 0,
Public = 1
}
public static void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId)
{
Callbacks = handlers;
EventHandlers staticEventHandlers = new EventHandlers();
staticEventHandlers.readyCallback += DiscordRpc.ReadyCallback;
staticEventHandlers.disconnectedCallback += DiscordRpc.DisconnectedCallback;
staticEventHandlers.errorCallback += DiscordRpc.ErrorCallback;
staticEventHandlers.joinCallback += DiscordRpc.JoinCallback;
staticEventHandlers.spectateCallback += DiscordRpc.SpectateCallback;
staticEventHandlers.requestCallback += DiscordRpc.RequestCallback;
InitializeInternal(applicationId, ref staticEventHandlers, autoRegister, optionalSteamId);
}
[DllImport("discord-rpc", EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)] [DllImport("discord-rpc", EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)]
static extern void InitializeInternal(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId); public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId);
[DllImport("discord-rpc", EntryPoint = "Discord_Shutdown", CallingConvention = CallingConvention.Cdecl)] [DllImport("discord-rpc", EntryPoint = "Discord_Shutdown", CallingConvention = CallingConvention.Cdecl)]
public static extern void Shutdown(); public static extern void Shutdown();
@ -144,7 +113,6 @@ public class DiscordRpc
public string partyId; /* max 128 bytes */ public string partyId; /* max 128 bytes */
public int partySize; public int partySize;
public int partyMax; public int partyMax;
public PartyPrivacy partyPrivacy;
public string matchSecret; /* max 128 bytes */ public string matchSecret; /* max 128 bytes */
public string joinSecret; /* max 128 bytes */ public string joinSecret; /* max 128 bytes */
public string spectateSecret; /* max 128 bytes */ public string spectateSecret; /* max 128 bytes */
@ -161,21 +129,20 @@ public class DiscordRpc
FreeMem(); FreeMem();
} }
_presence.state = StrToPtr(state); _presence.state = StrToPtr(state, 128);
_presence.details = StrToPtr(details); _presence.details = StrToPtr(details, 128);
_presence.startTimestamp = startTimestamp; _presence.startTimestamp = startTimestamp;
_presence.endTimestamp = endTimestamp; _presence.endTimestamp = endTimestamp;
_presence.largeImageKey = StrToPtr(largeImageKey); _presence.largeImageKey = StrToPtr(largeImageKey, 32);
_presence.largeImageText = StrToPtr(largeImageText); _presence.largeImageText = StrToPtr(largeImageText, 128);
_presence.smallImageKey = StrToPtr(smallImageKey); _presence.smallImageKey = StrToPtr(smallImageKey, 32);
_presence.smallImageText = StrToPtr(smallImageText); _presence.smallImageText = StrToPtr(smallImageText, 128);
_presence.partyId = StrToPtr(partyId); _presence.partyId = StrToPtr(partyId, 128);
_presence.partySize = partySize; _presence.partySize = partySize;
_presence.partyMax = partyMax; _presence.partyMax = partyMax;
_presence.partyPrivacy = (int)partyPrivacy; _presence.matchSecret = StrToPtr(matchSecret, 128);
_presence.matchSecret = StrToPtr(matchSecret); _presence.joinSecret = StrToPtr(joinSecret, 128);
_presence.joinSecret = StrToPtr(joinSecret); _presence.spectateSecret = StrToPtr(spectateSecret, 128);
_presence.spectateSecret = StrToPtr(spectateSecret);
_presence.instance = instance; _presence.instance = instance;
return _presence; return _presence;
@ -185,18 +152,16 @@ public class DiscordRpc
/// Returns a pointer to a representation of the given string with a size of maxbytes /// Returns a pointer to a representation of the given string with a size of maxbytes
/// </summary> /// </summary>
/// <param name="input">String to convert</param> /// <param name="input">String to convert</param>
/// <param name="maxbytes">Max number of bytes to use</param>
/// <returns>Pointer to the UTF-8 representation of <see cref="input"/></returns> /// <returns>Pointer to the UTF-8 representation of <see cref="input"/></returns>
private IntPtr StrToPtr(string input) private IntPtr StrToPtr(string input, int maxbytes)
{ {
if (string.IsNullOrEmpty(input)) return IntPtr.Zero; if (string.IsNullOrEmpty(input)) return IntPtr.Zero;
var convbytecnt = Encoding.UTF8.GetByteCount(input); var convstr = StrClampBytes(input, maxbytes);
var buffer = Marshal.AllocHGlobal(convbytecnt + 1); var convbytecnt = Encoding.UTF8.GetByteCount(convstr);
for (int i = 0; i < convbytecnt + 1; i++) var buffer = Marshal.AllocHGlobal(convbytecnt);
{
Marshal.WriteByte(buffer, i, 0);
}
_buffers.Add(buffer); _buffers.Add(buffer);
Marshal.Copy(Encoding.UTF8.GetBytes(input), 0, buffer, convbytecnt); Marshal.Copy(Encoding.UTF8.GetBytes(convstr), 0, buffer, convbytecnt);
return buffer; return buffer;
} }
@ -216,6 +181,30 @@ public class DiscordRpc
return Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(str)); return Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(str));
} }
/// <summary>
/// Clamp the string to the given byte length preserving null termination
/// </summary>
/// <param name="toclamp">string to clamp</param>
/// <param name="maxbytes">max bytes the resulting string should have (including null termination)</param>
/// <returns>null terminated string with a byte length less or equal to <see cref="maxbytes"/></returns>
private static string StrClampBytes(string toclamp, int maxbytes)
{
var str = StrToUtf8NullTerm(toclamp);
var strbytes = Encoding.UTF8.GetBytes(str);
if (strbytes.Length <= maxbytes)
{
return str;
}
var newstrbytes = new byte[] { };
Array.Copy(strbytes, 0, newstrbytes, 0, maxbytes - 1);
newstrbytes[newstrbytes.Length - 1] = 0;
newstrbytes[newstrbytes.Length - 2] = 0;
return Encoding.UTF8.GetString(newstrbytes);
}
/// <summary> /// <summary>
/// Free the allocated memory for conversion to <see cref="RichPresenceStruct"/> /// Free the allocated memory for conversion to <see cref="RichPresenceStruct"/>
/// </summary> /// </summary>

View File

@ -47,24 +47,19 @@ static void updateDiscordPresence()
discordPresence.partyId = "party1234"; discordPresence.partyId = "party1234";
discordPresence.partySize = 1; discordPresence.partySize = 1;
discordPresence.partyMax = 6; discordPresence.partyMax = 6;
discordPresence.partyPrivacy = DISCORD_PARTY_PUBLIC;
discordPresence.matchSecret = "xyzzy"; discordPresence.matchSecret = "xyzzy";
discordPresence.joinSecret = "join"; discordPresence.joinSecret = "join";
discordPresence.spectateSecret = "look"; discordPresence.spectateSecret = "look";
discordPresence.instance = 0; discordPresence.instance = 0;
Discord_UpdatePresence(&discordPresence); Discord_UpdatePresence(&discordPresence);
} } else {
else {
Discord_ClearPresence(); Discord_ClearPresence();
} }
} }
static void handleDiscordReady(const DiscordUser* connectedUser) static void handleDiscordReady(void)
{ {
printf("\nDiscord: connected to user %s#%s - %s\n", printf("\nDiscord: ready\n");
connectedUser->username,
connectedUser->discriminator,
connectedUser->userId);
} }
static void handleDiscordDisconnected(int errcode, const char* message) static void handleDiscordDisconnected(int errcode, const char* message)
@ -87,13 +82,13 @@ static void handleDiscordSpectate(const char* secret)
printf("\nDiscord: spectate (%s)\n", secret); printf("\nDiscord: spectate (%s)\n", secret);
} }
static void handleDiscordJoinRequest(const DiscordUser* request) static void handleDiscordJoinRequest(const DiscordJoinRequest* request)
{ {
int response = -1; int response = -1;
char yn[4]; char yn[4];
printf("\nDiscord: join request from %s#%s - %s\n", printf("\nDiscord: join request from %s - %s - %s\n",
request->username, request->username,
request->discriminator, request->avatar,
request->userId); request->userId);
do { do {
printf("Accept? (y/n)"); printf("Accept? (y/n)");
@ -157,8 +152,7 @@ static void gameLoop()
if (SendPresence) { if (SendPresence) {
printf("Clearing presence information.\n"); printf("Clearing presence information.\n");
SendPresence = 0; SendPresence = 0;
} } else {
else {
printf("Restoring presence information.\n"); printf("Restoring presence information.\n");
SendPresence = 1; SendPresence = 1;
} }

View File

@ -12,26 +12,19 @@ void FDiscordRpcModule::StartupModule()
#if defined(DISCORD_DYNAMIC_LIB) #if defined(DISCORD_DYNAMIC_LIB)
// Get the base directory of this plugin // Get the base directory of this plugin
FString BaseDir = IPluginManager::Get().FindPlugin("DiscordRpc")->GetBaseDir(); FString BaseDir = IPluginManager::Get().FindPlugin("DiscordRpc")->GetBaseDir();
const FString SDKDir = const FString SDKDir = FPaths::Combine(*BaseDir, TEXT("Source"), TEXT("ThirdParty"), TEXT("DiscordRpcLibrary"));
FPaths::Combine(*BaseDir, TEXT("Source"), TEXT("ThirdParty"), TEXT("DiscordRpcLibrary"));
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS
const FString LibName = TEXT("discord-rpc"); const FString LibName = TEXT("discord-rpc");
const FString LibDir = FPaths::Combine(*SDKDir, TEXT("Win64")); const FString LibDir = FPaths::Combine(*SDKDir, TEXT("Win64"));
if (!LoadDependency(LibDir, LibName, DiscordRpcLibraryHandle)) { if (!LoadDependency(LibDir, LibName, DiscordRpcLibraryHandle)) {
FMessageDialog::Open( FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT(LOCTEXT_NAMESPACE, "Failed to load DiscordRpc plugin. Plug-in will not be functional."));
EAppMsgType::Ok,
LOCTEXT(LOCTEXT_NAMESPACE,
"Failed to load DiscordRpc plugin. Plug-in will not be functional."));
FreeDependency(DiscordRpcLibraryHandle); FreeDependency(DiscordRpcLibraryHandle);
} }
#elif PLATFORM_MAC #elif PLATFORM_MAC
const FString LibName = TEXT("libdiscord-rpc"); const FString LibName = TEXT("libdiscord-rpc");
const FString LibDir = FPaths::Combine(*SDKDir, TEXT("Mac")); const FString LibDir = FPaths::Combine(*SDKDir, TEXT("Mac"));
if (!LoadDependency(LibDir, LibName, DiscordRpcLibraryHandle)) { if (!LoadDependency(LibDir, LibName, DiscordRpcLibraryHandle)) {
FMessageDialog::Open( FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT(LOCTEXT_NAMESPACE, "Failed to load DiscordRpc plugin. Plug-in will not be functional."));
EAppMsgType::Ok,
LOCTEXT(LOCTEXT_NAMESPACE,
"Failed to load DiscordRpc plugin. Plug-in will not be functional."));
FreeDependency(DiscordRpcLibraryHandle); FreeDependency(DiscordRpcLibraryHandle);
} }
#endif #endif
@ -56,7 +49,8 @@ bool FDiscordRpcModule::LoadDependency(const FString& Dir, const FString& Name,
Handle = FPlatformProcess::GetDllHandle(*Path); Handle = FPlatformProcess::GetDllHandle(*Path);
if (Handle == nullptr) { if (Handle == nullptr)
{
return false; return false;
} }
@ -65,7 +59,8 @@ bool FDiscordRpcModule::LoadDependency(const FString& Dir, const FString& Name,
void FDiscordRpcModule::FreeDependency(void*& Handle) void FDiscordRpcModule::FreeDependency(void*& Handle)
{ {
if (Handle != nullptr) { if (Handle != nullptr)
{
FPlatformProcess::FreeDllHandle(Handle); FPlatformProcess::FreeDllHandle(Handle);
Handle = nullptr; Handle = nullptr;
} }

View File

@ -6,22 +6,12 @@ DEFINE_LOG_CATEGORY(Discord)
static UDiscordRpc* self = nullptr; static UDiscordRpc* self = nullptr;
static void ReadyHandler(const DiscordUser* connectedUser) static void ReadyHandler()
{ {
FDiscordUserData ud; UE_LOG(Discord, Log, TEXT("Discord connected"));
ud.userId = ANSI_TO_TCHAR(connectedUser->userId);
ud.username = ANSI_TO_TCHAR(connectedUser->username);
ud.discriminator = ANSI_TO_TCHAR(connectedUser->discriminator);
ud.avatar = ANSI_TO_TCHAR(connectedUser->avatar);
UE_LOG(Discord,
Log,
TEXT("Discord connected to %s - %s#%s"),
*ud.userId,
*ud.username,
*ud.discriminator);
if (self) { if (self) {
self->IsConnected = true; self->IsConnected = true;
self->OnConnected.Broadcast(ud); self->OnConnected.Broadcast();
} }
} }
@ -62,27 +52,22 @@ static void SpectateGameHandler(const char* spectateSecret)
} }
} }
static void JoinRequestHandler(const DiscordUser* request) static void JoinRequestHandler(const DiscordJoinRequest* request)
{ {
FDiscordUserData ud; FDiscordJoinRequestData jr;
ud.userId = ANSI_TO_TCHAR(request->userId); jr.userId = ANSI_TO_TCHAR(request->userId);
ud.username = ANSI_TO_TCHAR(request->username); jr.username = ANSI_TO_TCHAR(request->username);
ud.discriminator = ANSI_TO_TCHAR(request->discriminator); jr.discriminator = ANSI_TO_TCHAR(request->discriminator);
ud.avatar = ANSI_TO_TCHAR(request->avatar); jr.avatar = ANSI_TO_TCHAR(request->avatar);
UE_LOG(Discord, UE_LOG(Discord, Log, TEXT("Discord join request from %s - %s#%s"), *jr.userId, *jr.username, *jr.discriminator);
Log,
TEXT("Discord join request from %s - %s#%s"),
*ud.userId,
*ud.username,
*ud.discriminator);
if (self) { if (self) {
self->OnJoinRequest.Broadcast(ud); self->OnJoinRequest.Broadcast(jr);
} }
} }
void UDiscordRpc::Initialize(const FString& applicationId, void UDiscordRpc::Initialize(const FString& applicationId,
bool autoRegister, bool autoRegister,
const FString& optionalSteamId) const FString& optionalSteamId)
{ {
self = this; self = this;
IsConnected = false; IsConnected = false;
@ -102,7 +87,7 @@ void UDiscordRpc::Initialize(const FString& applicationId,
auto appId = StringCast<ANSICHAR>(*applicationId); auto appId = StringCast<ANSICHAR>(*applicationId);
auto steamId = StringCast<ANSICHAR>(*optionalSteamId); auto steamId = StringCast<ANSICHAR>(*optionalSteamId);
Discord_Initialize( Discord_Initialize(
(const char*)appId.Get(), &handlers, autoRegister, (const char*)steamId.Get()); (const char*)appId.Get(), &handlers, autoRegister, (const char*)steamId.Get());
} }
void UDiscordRpc::Shutdown() void UDiscordRpc::Shutdown()
@ -153,7 +138,6 @@ void UDiscordRpc::UpdatePresence()
rp.endTimestamp = RichPresence.endTimestamp; rp.endTimestamp = RichPresence.endTimestamp;
rp.partySize = RichPresence.partySize; rp.partySize = RichPresence.partySize;
rp.partyMax = RichPresence.partyMax; rp.partyMax = RichPresence.partyMax;
rp.partyPrivacy = (int)RichPresence.partyPrivacy;
rp.instance = RichPresence.instance; rp.instance = RichPresence.instance;
Discord_UpdatePresence(&rp); Discord_UpdatePresence(&rp);

View File

@ -11,7 +11,7 @@
* Ask to join callback data * Ask to join callback data
*/ */
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct FDiscordUserData { struct FDiscordJoinRequestData {
GENERATED_USTRUCT_BODY() GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
@ -24,35 +24,15 @@ struct FDiscordUserData {
FString avatar; FString avatar;
}; };
/**
* Valid response codes for Respond function
*/
UENUM(BlueprintType)
enum class EDiscordJoinResponseCodes : uint8
{
DISCORD_REPLY_NO UMETA(DisplayName="No"),
DISCORD_REPLY_YES UMETA(DisplayName="Yes"),
DISCORD_REPLY_IGNORE UMETA(DisplayName="Ignore")
};
/**
* Valid party privacy values
*/
UENUM(BlueprintType)
enum class EDiscordPartyPrivacy: uint8
{
DISCORD_PARTY_PRIVATE UMETA(DisplayName="Private"),
DISCORD_PARTY_PUBLIC UMETA(DisplayName="Public")
};
DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All); DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordConnected, const FDiscordUserData&, joinRequest); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDiscordConnected);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordDisconnected, int, errorCode, const FString&, errorMessage); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordDisconnected, int, errorCode, const FString&, errorMessage);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordErrored, int, errorCode, const FString&, errorMessage); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordErrored, int, errorCode, const FString&, errorMessage);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoin, const FString&, joinSecret); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoin, const FString&, joinSecret);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordSpectate, const FString&, spectateSecret); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordSpectate, const FString&, spectateSecret);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoinRequest, const FDiscordUserData&, joinRequest); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoinRequest, const FDiscordJoinRequestData&, joinRequest);
// clang-format on // clang-format on
@ -87,8 +67,6 @@ struct FDiscordRichPresence {
UPROPERTY(BlueprintReadWrite) UPROPERTY(BlueprintReadWrite)
int partyMax; int partyMax;
UPROPERTY(BlueprintReadWrite) UPROPERTY(BlueprintReadWrite)
EDiscordPartyPrivacy partyPrivacy;
UPROPERTY(BlueprintReadWrite)
FString matchSecret; FString matchSecret;
UPROPERTY(BlueprintReadWrite) UPROPERTY(BlueprintReadWrite)
FString joinSecret; FString joinSecret;

View File

@ -1,17 +1,17 @@
#pragma once #pragma once
#if defined(DISCORD_DYNAMIC_LIB) #if defined(DISCORD_DYNAMIC_LIB)
#if defined(_WIN32) # if defined(_WIN32)
#if defined(DISCORD_BUILDING_SDK) # if defined(DISCORD_BUILDING_SDK)
#define DISCORD_EXPORT __declspec(dllexport) # define DISCORD_EXPORT __declspec(dllexport)
# else
# define DISCORD_EXPORT __declspec(dllimport)
# endif
# else
# define DISCORD_EXPORT __attribute__((visibility("default")))
# endif
#else #else
#define DISCORD_EXPORT __declspec(dllimport) # define DISCORD_EXPORT
#endif
#else
#define DISCORD_EXPORT __attribute__((visibility("default")))
#endif
#else
#define DISCORD_EXPORT
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -35,34 +35,31 @@ typedef struct DiscordRichPresence {
const char* partyId; /* max 128 bytes */ const char* partyId; /* max 128 bytes */
int partySize; int partySize;
int partyMax; int partyMax;
int partyPrivacy;
const char* matchSecret; /* max 128 bytes */ const char* matchSecret; /* max 128 bytes */
const char* joinSecret; /* max 128 bytes */ const char* joinSecret; /* max 128 bytes */
const char* spectateSecret; /* max 128 bytes */ const char* spectateSecret; /* max 128 bytes */
int8_t instance; int8_t instance;
} DiscordRichPresence; } DiscordRichPresence;
typedef struct DiscordUser { typedef struct DiscordJoinRequest {
const char* userId; const char* userId;
const char* username; const char* username;
const char* discriminator; const char* discriminator;
const char* avatar; const char* avatar;
} DiscordUser; } DiscordJoinRequest;
typedef struct DiscordEventHandlers { typedef struct DiscordEventHandlers {
void (*ready)(const DiscordUser* request); void (*ready)(void);
void (*disconnected)(int errorCode, const char* message); void (*disconnected)(int errorCode, const char* message);
void (*errored)(int errorCode, const char* message); void (*errored)(int errorCode, const char* message);
void (*joinGame)(const char* joinSecret); void (*joinGame)(const char* joinSecret);
void (*spectateGame)(const char* spectateSecret); void (*spectateGame)(const char* spectateSecret);
void (*joinRequest)(const DiscordUser* request); void (*joinRequest)(const DiscordJoinRequest* request);
} DiscordEventHandlers; } DiscordEventHandlers;
#define DISCORD_REPLY_NO 0 #define DISCORD_REPLY_NO 0
#define DISCORD_REPLY_YES 1 #define DISCORD_REPLY_YES 1
#define DISCORD_REPLY_IGNORE 2 #define DISCORD_REPLY_IGNORE 2
#define DISCORD_PARTY_PRIVATE 0
#define DISCORD_PARTY_PUBLIC 1
DISCORD_EXPORT void Discord_Initialize(const char* applicationId, DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers, DiscordEventHandlers* handlers,

View File

@ -2,7 +2,6 @@ include_directories(${PROJECT_SOURCE_DIR}/include)
option(ENABLE_IO_THREAD "Start up a separate I/O thread, otherwise I'd need to call an update function" ON) option(ENABLE_IO_THREAD "Start up a separate I/O thread, otherwise I'd need to call an update function" ON)
option(USE_STATIC_CRT "Use /MT[d] for dynamic library" OFF) option(USE_STATIC_CRT "Use /MT[d] for dynamic library" OFF)
option(WARNINGS_AS_ERRORS "When enabled, compiles with `-Werror` (on *nix platforms)." OFF)
set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD 14)
@ -72,23 +71,12 @@ if(UNIX)
add_library(discord-rpc ${BASE_RPC_SRC}) add_library(discord-rpc ${BASE_RPC_SRC})
target_link_libraries(discord-rpc PUBLIC pthread) target_link_libraries(discord-rpc PUBLIC pthread)
if (APPLE)
target_link_libraries(discord-rpc PRIVATE "-framework AppKit, -mmacosx-version-min=10.10")
endif (APPLE)
target_compile_options(discord-rpc PRIVATE target_compile_options(discord-rpc PRIVATE
-g -g
-Wall -Wall
-Wextra -Wextra
-Wpedantic -Wpedantic
) -Werror
if (${WARNINGS_AS_ERRORS})
target_compile_options(discord-rpc PRIVATE -Werror)
endif (${WARNINGS_AS_ERRORS})
target_compile_options(discord-rpc PRIVATE
-Wno-unknown-pragmas # pragma push thing doesn't work on clang -Wno-unknown-pragmas # pragma push thing doesn't work on clang
-Wno-old-style-cast # it's fine -Wno-old-style-cast # it's fine
-Wno-c++98-compat # that was almost 2 decades ago -Wno-c++98-compat # that was almost 2 decades ago

View File

@ -118,8 +118,5 @@ bool BaseConnection::Read(void* data, size_t length)
} }
Close(); Close();
} }
else if (res == 0) {
Close();
}
return res == (int)length; return res == (int)length;
} }

View File

@ -33,15 +33,13 @@ extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const
char exePath[1024]; char exePath[1024];
if (!command || !command[0]) { if (!command || !command[0]) {
ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath)); if (readlink("/proc/self/exe", exePath, sizeof(exePath)) <= 0) {
if (size <= 0 || size >= (ssize_t)sizeof(exePath)) {
return; return;
} }
exePath[size] = '\0';
command = exePath; command = exePath;
} }
const char* desktopFileFormat = "[Desktop Entry]\n" const char* destopFileFormat = "[Desktop Entry]\n"
"Name=Game %s\n" "Name=Game %s\n"
"Exec=%s %%u\n" // note: it really wants that %u in there "Exec=%s %%u\n" // note: it really wants that %u in there
"Type=Application\n" "Type=Application\n"
@ -50,7 +48,7 @@ extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const
"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), destopFileFormat, applicationId, command, applicationId);
if (fileLen <= 0) { if (fileLen <= 0) {
return; return;
} }
@ -93,8 +91,7 @@ extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const
} }
} }
extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
const char* steamId)
{ {
char command[256]; char command[256];
sprintf(command, "xdg-open steam://rungameid/%s", steamId); sprintf(command, "xdg-open steam://rungameid/%s", steamId);

View File

@ -7,6 +7,7 @@
#define NOIME #define NOIME
#include <windows.h> #include <windows.h>
#include <psapi.h> #include <psapi.h>
#include <cwchar>
#include <cstdio> #include <cstdio>
/** /**
@ -19,22 +20,19 @@
* The entire function is rewritten * The entire function is rewritten
*/ */
#ifdef __MINGW32__ #ifdef __MINGW32__
#include <wchar.h>
/// strsafe.h fixes /// strsafe.h fixes
static HRESULT StringCbPrintfW(LPWSTR pszDest, size_t cbDest, LPCWSTR pszFormat, ...) static HRESULT StringCbPrintfW(LPWSTR pszDest, size_t cbDest, LPCWSTR pszFormat, ...)
{ {
HRESULT ret; HRESULT ret;
va_list va; va_list va;
va_start(va, pszFormat); va_start(va, pszFormat);
cbDest /= 2; // Size is divided by 2 to convert from bytes to wide characters - causes segfault cbDest /= 2; // Size is divided by 2 to convert from bytes to wide characters - causes segfault othervise
// othervise
ret = vsnwprintf(pszDest, cbDest, pszFormat, va); ret = vsnwprintf(pszDest, cbDest, pszFormat, va);
pszDest[cbDest - 1] = 0; // Terminate the string in case a buffer overflow; -1 will be returned pszDest[cbDest - 1] = 0; // Terminate the string in case a buffer overflow; -1 will be returned
va_end(va); va_end(va);
return ret; return ret;
} }
#else #else
#include <cwchar>
#include <strsafe.h> #include <strsafe.h>
#endif // __MINGW32__ #endif // __MINGW32__
@ -46,24 +44,17 @@ static HRESULT StringCbPrintfW(LPWSTR pszDest, size_t cbDest, LPCWSTR pszFormat,
#undefine RegSetKeyValueW #undefine RegSetKeyValueW
#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])
if ((ret = RegCreateKeyExW(hkey, subkey, 0, 0, 0, KEY_ALL_ACCESS, 0, &hsubkey, 0)) != {
ERROR_SUCCESS) if((ret = RegCreateKeyExW(hkey, subkey, 0, 0, 0, KEY_ALL_ACCESS, 0, &hsubkey, 0)) != ERROR_SUCCESS) return ret;
return ret;
htkey = hsubkey; htkey = hsubkey;
} }
ret = RegSetValueExW(htkey, name, 0, type, (const BYTE*)data, len); ret = RegSetValueExW(htkey, name, 0, type, (const BYTE*)data, len);
if (hsubkey && hsubkey != hkey) if (hsubkey && hsubkey != hkey) RegCloseKey(hsubkey);
RegCloseKey(hsubkey);
return ret; return ret;
} }
@ -81,7 +72,7 @@ static void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* comma
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command); StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command);
} }
else { else {
// StringCbCopyW(openCommand, sizeof(openCommand), exeFilePath); //StringCbCopyW(openCommand, sizeof(openCommand), exeFilePath);
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", exeFilePath); StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", exeFilePath);
} }
@ -147,8 +138,7 @@ extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const
Discord_RegisterW(appId, wcommand); Discord_RegisterW(appId, wcommand);
} }
extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
const char* steamId)
{ {
wchar_t appId[32]; wchar_t appId[32];
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);

View File

@ -32,7 +32,7 @@ struct QueuedMessage {
} }
}; };
struct User { struct JoinRequest {
// 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];
@ -54,7 +54,6 @@ static std::atomic_bool WasJustDisconnected{false};
static std::atomic_bool GotErrorMessage{false}; static std::atomic_bool GotErrorMessage{false};
static std::atomic_bool WasJoinGame{false}; static std::atomic_bool WasJoinGame{false};
static std::atomic_bool WasSpectateGame{false}; static std::atomic_bool WasSpectateGame{false};
static std::atomic_bool UpdatePresence{false};
static char JoinGameSecret[256]; static char JoinGameSecret[256];
static char SpectateGameSecret[256]; static char SpectateGameSecret[256];
static int LastErrorCode{0}; static int LastErrorCode{0};
@ -65,8 +64,7 @@ static std::mutex PresenceMutex;
static std::mutex HandlerMutex; static std::mutex HandlerMutex;
static QueuedMessage QueuedPresence{}; static QueuedMessage QueuedPresence{};
static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue; static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
static MsgQueue<User, JoinQueueSize> JoinAskQueue; static MsgQueue<JoinRequest, JoinQueueSize> JoinAskQueue;
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
@ -90,11 +88,10 @@ public:
keepRunning.store(true); keepRunning.store(true);
ioThread = std::thread([&]() { ioThread = std::thread([&]() {
const std::chrono::duration<int64_t, std::milli> maxWait{500LL}; const std::chrono::duration<int64_t, std::milli> maxWait{500LL};
Discord_UpdateConnection();
while (keepRunning.load()) { while (keepRunning.load()) {
Discord_UpdateConnection();
std::unique_lock<std::mutex> lock(waitForIOMutex); std::unique_lock<std::mutex> lock(waitForIOMutex);
waitForIOActivity.wait_for(lock, maxWait); waitForIOActivity.wait_for(lock, maxWait);
Discord_UpdateConnection();
} }
}); });
} }
@ -120,7 +117,7 @@ public:
void Notify() {} void Notify() {}
}; };
#endif // DISCORD_DISABLE_IO_THREAD #endif // DISCORD_DISABLE_IO_THREAD
static IoThreadHolder* IoThread{nullptr}; static IoThreadHolder IoThread;
static void UpdateReconnectTime() static void UpdateReconnectTime()
{ {
@ -215,17 +212,17 @@ static void Discord_UpdateConnection(void)
} }
// writes // writes
if (UpdatePresence.exchange(false) && QueuedPresence.length) { if (QueuedPresence.length) {
QueuedMessage local; QueuedMessage local;
{ {
std::lock_guard<std::mutex> guard(PresenceMutex); std::lock_guard<std::mutex> guard(PresenceMutex);
local.Copy(QueuedPresence); local.Copy(QueuedPresence);
QueuedPresence.length = 0;
} }
if (!Connection->Write(local.buffer, local.length)) { if (!Connection->Write(local.buffer, local.length)) {
// if we fail to send, requeue // if we fail to send, requeue
std::lock_guard<std::mutex> guard(PresenceMutex); std::lock_guard<std::mutex> guard(PresenceMutex);
QueuedPresence.Copy(local); QueuedPresence.Copy(local);
UpdatePresence.exchange(true);
} }
} }
@ -239,9 +236,7 @@ static void Discord_UpdateConnection(void)
static void SignalIOActivity() static void SignalIOActivity()
{ {
if (IoThread != nullptr) { IoThread.Notify();
IoThread->Notify();
}
} }
static bool RegisterForEvent(const char* evtName) static bool RegisterForEvent(const char* evtName)
@ -275,11 +270,6 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
int autoRegister, int autoRegister,
const char* optionalSteamId) const char* optionalSteamId)
{ {
IoThread = new (std::nothrow) IoThreadHolder();
if (IoThread == nullptr) {
return;
}
if (autoRegister) { if (autoRegister) {
if (optionalSteamId && optionalSteamId[0]) { if (optionalSteamId && optionalSteamId[0]) {
Discord_RegisterSteamGame(applicationId, optionalSteamId); Discord_RegisterSteamGame(applicationId, optionalSteamId);
@ -302,6 +292,7 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
} }
Handlers = {}; Handlers = {};
} }
if (Connection) { if (Connection) {
@ -309,42 +300,23 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
} }
Connection = RpcConnection::Create(applicationId); Connection = RpcConnection::Create(applicationId);
Connection->onConnect = [](JsonDocument& readyMessage) { Connection->onConnect = []() {
Discord_UpdateHandlers(&QueuedHandlers); Discord_UpdateHandlers(&QueuedHandlers);
if (QueuedPresence.length > 0) {
UpdatePresence.exchange(true);
SignalIOActivity();
}
auto data = GetObjMember(&readyMessage, "data");
auto user = GetObjMember(data, "user");
auto userId = GetStrMember(user, "id");
auto username = GetStrMember(user, "username");
auto avatar = GetStrMember(user, "avatar");
if (userId && username) {
StringCopy(connectedUser.userId, userId);
StringCopy(connectedUser.username, username);
auto discriminator = GetStrMember(user, "discriminator");
if (discriminator) {
StringCopy(connectedUser.discriminator, discriminator);
}
if (avatar) {
StringCopy(connectedUser.avatar, avatar);
}
else {
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);
{
std::lock_guard<std::mutex> guard(HandlerMutex);
Handlers = {};
}
WasJustDisconnected.exchange(true); WasJustDisconnected.exchange(true);
UpdateReconnectTime(); UpdateReconnectTime();
}; };
IoThread->Start(); IoThread.Start();
} }
extern "C" DISCORD_EXPORT void Discord_Shutdown(void) extern "C" DISCORD_EXPORT void Discord_Shutdown(void)
@ -355,14 +327,7 @@ extern "C" DISCORD_EXPORT void Discord_Shutdown(void)
Connection->onConnect = nullptr; Connection->onConnect = nullptr;
Connection->onDisconnect = nullptr; Connection->onDisconnect = nullptr;
Handlers = {}; Handlers = {};
QueuedPresence.length = 0; IoThread.Stop();
UpdatePresence.exchange(false);
if (IoThread != nullptr) {
IoThread->Stop();
delete IoThread;
IoThread = nullptr;
}
RpcConnection::Destroy(Connection); RpcConnection::Destroy(Connection);
} }
@ -371,8 +336,7 @@ extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence*
{ {
std::lock_guard<std::mutex> guard(PresenceMutex); std::lock_guard<std::mutex> guard(PresenceMutex);
QueuedPresence.length = JsonWriteRichPresenceObj( QueuedPresence.length = JsonWriteRichPresenceObj(
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence); QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
UpdatePresence.exchange(true);
} }
SignalIOActivity(); SignalIOActivity();
} }
@ -421,11 +385,7 @@ extern "C" DISCORD_EXPORT 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, Handlers.ready();
connectedUser.username,
connectedUser.discriminator,
connectedUser.avatar};
Handlers.ready(&du);
} }
} }
@ -460,8 +420,8 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
{ {
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}; DiscordJoinRequest djr{req->userId, req->username, req->discriminator, req->avatar};
Handlers.joinRequest(&du); Handlers.joinRequest(&djr);
} }
} }
JoinAskQueue.CommitSend(); JoinAskQueue.CommitSend();
@ -479,13 +439,14 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
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) { \ #define HANDLE_EVENT_REGISTRATION(handler_name, event) \
RegisterForEvent(event); \ if (!Handlers.handler_name && newHandlers->handler_name) { \
} \ RegisterForEvent(event); \
else if (Handlers.handler_name && !newHandlers->handler_name) { \ } \
DeregisterForEvent(event); \ else if (Handlers.handler_name && !newHandlers->handler_name) { \
} DeregisterForEvent(event); \
}
std::lock_guard<std::mutex> guard(HandlerMutex); std::lock_guard<std::mutex> guard(HandlerMutex);
HANDLE_EVENT_REGISTRATION(joinGame, "ACTIVITY_JOIN") HANDLE_EVENT_REGISTRATION(joinGame, "ACTIVITY_JOIN")
@ -496,7 +457,8 @@ extern "C" DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* newH
Handlers = *newHandlers; Handlers = *newHandlers;
} }
else { else
{
std::lock_guard<std::mutex> guard(HandlerMutex); std::lock_guard<std::mutex> guard(HandlerMutex);
Handlers = {}; Handlers = {};
} }

View File

@ -7,7 +7,7 @@
template <typename ElementType, size_t QueueSize> template <typename ElementType, size_t QueueSize>
class MsgQueue { class MsgQueue {
ElementType queue_[QueueSize]; ElementType queue_[QueueSize]{};
std::atomic_uint nextAdd_{0}; std::atomic_uint nextAdd_{0};
std::atomic_uint nextSend_{0}; std::atomic_uint nextSend_{0};
std::atomic_uint pendingSends_{0}; std::atomic_uint pendingSends_{0};

View File

@ -26,8 +26,12 @@ void RpcConnection::Open()
return; return;
} }
if (state == State::Disconnected && !connection->Open()) { if (state == State::Disconnected) {
return; if (connection->Open()) {
}
else {
return;
}
} }
if (state == State::SentHandshake) { if (state == State::SentHandshake) {
@ -38,7 +42,7 @@ void RpcConnection::Open()
if (cmd && evt && !strcmp(cmd, "DISPATCH") && !strcmp(evt, "READY")) { if (cmd && evt && !strcmp(cmd, "DISPATCH") && !strcmp(evt, "READY")) {
state = State::Connected; state = State::Connected;
if (onConnect) { if (onConnect) {
onConnect(message); onConnect();
} }
} }
} }

View File

@ -40,7 +40,7 @@ struct RpcConnection {
BaseConnection* connection{nullptr}; BaseConnection* connection{nullptr};
State state{State::Disconnected}; State state{State::Disconnected};
void (*onConnect)(JsonDocument& message){nullptr}; void (*onConnect)(){nullptr};
void (*onDisconnect)(int errorCode, const char* message){nullptr}; void (*onDisconnect)(int errorCode, const char* message){nullptr};
char appId[64]{}; char appId[64]{};
int lastErrorCode{0}; int lastErrorCode{0};

View File

@ -102,7 +102,8 @@ size_t JsonWriteRichPresenceObj(char* dest,
WriteKey(writer, "pid"); WriteKey(writer, "pid");
writer.Int(pid); writer.Int(pid);
if (presence != nullptr) { if (presence != nullptr)
{
WriteObject activity(writer, "activity"); WriteObject activity(writer, "activity");
WriteOptionalString(writer, "state", presence->state); WriteOptionalString(writer, "state", presence->state);
@ -134,7 +135,7 @@ size_t JsonWriteRichPresenceObj(char* dest,
} }
if ((presence->partyId && presence->partyId[0]) || presence->partySize || if ((presence->partyId && presence->partyId[0]) || presence->partySize ||
presence->partyMax || presence->partyPrivacy) { presence->partyMax) {
WriteObject party(writer, "party"); WriteObject party(writer, "party");
WriteOptionalString(writer, "id", presence->partyId); WriteOptionalString(writer, "id", presence->partyId);
if (presence->partySize && presence->partyMax) { if (presence->partySize && presence->partyMax) {
@ -142,11 +143,6 @@ size_t JsonWriteRichPresenceObj(char* dest,
writer.Int(presence->partySize); writer.Int(presence->partySize);
writer.Int(presence->partyMax); writer.Int(presence->partyMax);
} }
if (presence->partyPrivacy) {
WriteKey(writer, "privacy");
writer.Int(presence->partyPrivacy);
}
} }
if ((presence->matchSecret && presence->matchSecret[0]) || if ((presence->matchSecret && presence->matchSecret[0]) ||

View File

@ -10,7 +10,7 @@
#pragma warning(disable : 4464) // relative include path contains #pragma warning(disable : 4464) // relative include path contains
#pragma warning(disable : 4668) // is not defined as a preprocessor macro #pragma warning(disable : 4668) // is not defined as a preprocessor macro
#pragma warning(disable : 6313) // Incorrect operator #pragma warning(disable : 6313) // Incorrect operator
#endif // __MINGW32__ #endif // __MINGW32__
#include "rapidjson/document.h" #include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h" #include "rapidjson/stringbuffer.h"