Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
087282cd4b | |||
7e5d57e6fd | |||
f3bd411b99 | |||
8e0c7848a6 | |||
e7f9396807 | |||
ad0b844672 | |||
d279c24c6a | |||
d9caf72e9a | |||
e8091f5137 | |||
4055565147 | |||
578eb6de7c | |||
4e61b9c82c | |||
8ec10dc011 | |||
f5f2d69a72 | |||
453222075b | |||
c4201806cf | |||
ccf04d21f5 | |||
c7b4e6b2fc | |||
eee5085e9b |
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
/.vscode/
|
/.vscode/
|
||||||
/thirdparty/
|
/thirdparty/
|
||||||
.vs/
|
.vs/
|
||||||
|
.DS_Store
|
@ -28,23 +28,23 @@ endif(CLANG_FORMAT_CMD)
|
|||||||
|
|
||||||
# thirdparty stuff
|
# thirdparty stuff
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND mkdir ${CMAKE_SOURCE_DIR}/thirdparty
|
COMMAND mkdir ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty
|
||||||
ERROR_QUIET
|
ERROR_QUIET
|
||||||
)
|
)
|
||||||
|
|
||||||
find_file(RAPIDJSONTEST NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
|
find_file(RAPIDJSONTEST NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
|
||||||
if (NOT RAPIDJSONTEST)
|
if (NOT RAPIDJSONTEST)
|
||||||
message("no rapidjson, download")
|
message("no rapidjson, download")
|
||||||
set(RJ_TAR_FILE ${CMAKE_SOURCE_DIR}/thirdparty/v1.1.0.tar.gz)
|
set(RJ_TAR_FILE ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/v1.1.0.tar.gz)
|
||||||
file(DOWNLOAD https://github.com/miloyip/rapidjson/archive/v1.1.0.tar.gz ${RJ_TAR_FILE})
|
file(DOWNLOAD https://github.com/miloyip/rapidjson/archive/v1.1.0.tar.gz ${RJ_TAR_FILE})
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND ${CMAKE_COMMAND} -E tar xzf ${RJ_TAR_FILE}
|
COMMAND ${CMAKE_COMMAND} -E tar xzf ${RJ_TAR_FILE}
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/thirdparty
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty
|
||||||
)
|
)
|
||||||
file(REMOVE ${RJ_TAR_FILE})
|
file(REMOVE ${RJ_TAR_FILE})
|
||||||
endif(NOT RAPIDJSONTEST)
|
endif(NOT RAPIDJSONTEST)
|
||||||
|
|
||||||
find_file(RAPIDJSON NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
|
find_file(RAPIDJSON NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
|
||||||
|
|
||||||
add_library(rapidjson STATIC IMPORTED ${RAPIDJSON})
|
add_library(rapidjson STATIC IMPORTED ${RAPIDJSON})
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ have callbacks for where a more complete game would do more things (joining, spe
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The most up to date documentation for Rich Presence can always be found in our [developer site](https://discordapp.com/developers/docs/rich-presence/how-to)!
|
The most up to date documentation for Rich Presence can always be found on our [developer site](https://discordapp.com/developers/docs/rich-presence/how-to)! If you're interested in rolling your own native implementation of Rich Presence via IPC sockets instead of using our SDK—hey, you've got free time, right?—check out the ["Hard Mode" documentation](https://github.com/discordapp/discord-rpc/blob/master/documentation/hard-mode.md).
|
||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
@ -57,11 +57,11 @@ This is a text adventure "game" that inits/deinits the connection to Discord, an
|
|||||||
|
|
||||||
## Sample: button-clicker
|
## Sample: button-clicker
|
||||||
|
|
||||||
This is a sample [Unity](https://unity3d.com/) project that wraps a DLL version of the library, and sends presence updates when you click on a button.
|
This is a sample [Unity](https://unity3d.com/) project that wraps a DLL version of the library, and sends presence updates when you click on a button. Run `python build.py unity` in the root directory to build the correct library files and place them in their respective folders.
|
||||||
|
|
||||||
## Sample: unrealstatus
|
## Sample: unrealstatus
|
||||||
|
|
||||||
This is a sample [Unreal](https://www.unrealengine.com) project that wraps the DLL version of the library with an Unreal plugin, exposes a blueprint class for interacting with it, and uses that to make a very simple UI.
|
This is a sample [Unreal](https://www.unrealengine.com) project that wraps the DLL version of the library with an Unreal plugin, exposes a blueprint class for interacting with it, and uses that to make a very simple UI. Run `python build.py unreal` in the root directory to build the correct library files and place them in their respective folders.
|
||||||
|
|
||||||
## Wrappers and Implementations
|
## Wrappers and Implementations
|
||||||
|
|
||||||
|
92
build.py
92
build.py
@ -66,15 +66,9 @@ def cli(ctx, clean):
|
|||||||
ctx.invoke(archive)
|
ctx.invoke(archive)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
|
||||||
def unity():
|
|
||||||
""" todo: build unity project """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def for_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(
|
ctx.invoke(
|
||||||
libs,
|
libs,
|
||||||
@ -84,6 +78,44 @@ def for_unity(ctx):
|
|||||||
skip_formatter=True,
|
skip_formatter=True,
|
||||||
just_release=True
|
just_release=True
|
||||||
)
|
)
|
||||||
|
BUILDS = []
|
||||||
|
|
||||||
|
click.echo('--- Copying libs and header into unity example')
|
||||||
|
UNITY_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'button-clicker', 'Assets', 'Plugins')
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
LIBRARY_NAME = 'discord-rpc.dll'
|
||||||
|
BUILD_64_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release')
|
||||||
|
UNITY_64_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86_64')
|
||||||
|
BUILDS.append({BUILD_64_BASE_PATH: UNITY_64_DLL_PATH})
|
||||||
|
|
||||||
|
BUILD_32_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win32-dynamic', 'src', 'Release')
|
||||||
|
UNITY_32_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86')
|
||||||
|
BUILDS.append({BUILD_32_BASE_PATH: UNITY_32_DLL_PATH})
|
||||||
|
|
||||||
|
elif sys.platform == 'darwin':
|
||||||
|
LIBRARY_NAME = 'discord-rpc.bundle'
|
||||||
|
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'osx-dynamic', 'src')
|
||||||
|
UNITY_DLL_PATH = UNITY_PROJECT_PATH
|
||||||
|
os.rename(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})
|
||||||
|
|
||||||
|
elif sys.platform.startswith('linux'):
|
||||||
|
LIBRARY_NAME = 'discord-rpc.so'
|
||||||
|
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'linux-dynamic', 'src')
|
||||||
|
UNITY_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86')
|
||||||
|
os.rename(os.path.join(BUILD_BASE_PATH, 'libdiscord-rpc.so'), os.path.join(BUILD_BASE_PATH, 'discord-rpc.so'))
|
||||||
|
|
||||||
|
BUILDS.append({BUILD_BASE_PATH: UNITY_DLL_PATH})
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception('Unsupported platform ' + sys.platform)
|
||||||
|
|
||||||
|
for build in BUILDS:
|
||||||
|
for i in build:
|
||||||
|
mkdir_p(build[i])
|
||||||
|
shutil.copy(os.path.join(i, LIBRARY_NAME), build[i])
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@ -98,23 +130,45 @@ def unreal(ctx):
|
|||||||
skip_formatter=True,
|
skip_formatter=True,
|
||||||
just_release=True
|
just_release=True
|
||||||
)
|
)
|
||||||
|
BUILDS = []
|
||||||
|
|
||||||
click.echo('--- Copying libs and header into unreal example')
|
click.echo('--- Copying libs and header into unreal example')
|
||||||
|
|
||||||
UNREAL_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc')
|
UNREAL_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc')
|
||||||
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release')
|
|
||||||
|
|
||||||
UNREAL_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win64')
|
|
||||||
mkdir_p(UNREAL_DLL_PATH)
|
|
||||||
shutil.copy(os.path.join(BUILD_BASE_PATH, 'discord-rpc.dll'), UNREAL_DLL_PATH)
|
|
||||||
|
|
||||||
UNREAL_INCLUDE_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Include')
|
UNREAL_INCLUDE_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Include')
|
||||||
mkdir_p(UNREAL_INCLUDE_PATH)
|
mkdir_p(UNREAL_INCLUDE_PATH)
|
||||||
shutil.copy(os.path.join(SCRIPT_PATH, 'include', 'discord-rpc.h'), UNREAL_INCLUDE_PATH)
|
shutil.copy(os.path.join(SCRIPT_PATH, 'include', 'discord_rpc.h'), UNREAL_INCLUDE_PATH)
|
||||||
|
|
||||||
UNREAL_LIB_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win64')
|
if sys.platform.startswith('win'):
|
||||||
mkdir_p(UNREAL_LIB_PATH)
|
LIBRARY_NAME = 'discord-rpc.lib'
|
||||||
shutil.copy(os.path.join(BUILD_BASE_PATH, 'discord-rpc.lib'), UNREAL_LIB_PATH)
|
BUILD_64_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release')
|
||||||
|
UNREAL_64_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win64')
|
||||||
|
BUILDS.append({BUILD_64_BASE_PATH: UNREAL_64_DLL_PATH})
|
||||||
|
|
||||||
|
BUILD_32_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win32-dynamic', 'src', 'Release')
|
||||||
|
UNREAL_32_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win32')
|
||||||
|
BUILDS.append({BUILD_32_BASE_PATH: UNREAL_32_DLL_PATH})
|
||||||
|
|
||||||
|
elif sys.platform == 'darwin':
|
||||||
|
LIBRARY_NAME = 'libdiscord-rpc.dylib'
|
||||||
|
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'osx-dynamic', 'src')
|
||||||
|
UNREAL_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Mac')
|
||||||
|
|
||||||
|
BUILDS.append({BUILD_BASE_PATH: UNREAL_DLL_PATH})
|
||||||
|
|
||||||
|
elif sys.platform.startswith('linux'):
|
||||||
|
LIBRARY_NAME = 'libdiscord-rpc.so'
|
||||||
|
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'linux-dynamic', 'src')
|
||||||
|
UNREAL_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Linux')
|
||||||
|
|
||||||
|
BUILDS.append({BUILD_BASE_PATH: UNREAL_DLL_PATH})
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception('Unsupported platform ' + sys.platform)
|
||||||
|
|
||||||
|
for build in BUILDS:
|
||||||
|
for i in build:
|
||||||
|
mkdir_p(build[i])
|
||||||
|
shutil.copy(os.path.join(i, LIBRARY_NAME), build[i])
|
||||||
|
|
||||||
|
|
||||||
def build_lib(build_name, generator, options, just_release):
|
def build_lib(build_name, generator, options, just_release):
|
||||||
@ -170,7 +224,7 @@ def sign():
|
|||||||
sign_command_base = [
|
sign_command_base = [
|
||||||
tool,
|
tool,
|
||||||
'sign',
|
'sign',
|
||||||
'/n', 'Hammer & Chisel Inc.',
|
'/n', 'Discord Inc.',
|
||||||
'/a',
|
'/a',
|
||||||
'/tr', 'http://timestamp.digicert.com/rfc3161',
|
'/tr', 'http://timestamp.digicert.com/rfc3161',
|
||||||
'/as',
|
'/as',
|
||||||
|
@ -93,8 +93,7 @@ And third is the `ACTIVITY_JOIN_REQUEST` event:
|
|||||||
"username": "Mason",
|
"username": "Mason",
|
||||||
"discriminator": "1337",
|
"discriminator": "1337",
|
||||||
"avatar": "a_bab14f271d565501444b2ca3be944b25"
|
"avatar": "a_bab14f271d565501444b2ca3be944b25"
|
||||||
},
|
}
|
||||||
"secret": "e459ca99273f59909dd16ed97865f3ad"
|
|
||||||
},
|
},
|
||||||
"evt": "ACTIVITY_JOIN_REQUEST"
|
"evt": "ACTIVITY_JOIN_REQUEST"
|
||||||
}
|
}
|
||||||
@ -125,3 +124,41 @@ In order to receive these events, you need to [subscribe](https://discordapp.com
|
|||||||
"cmd": "SUBSCRIBE"
|
"cmd": "SUBSCRIBE"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To unsubscribe from these events, resend with the command `UNSUBSCRIBE`
|
||||||
|
|
||||||
|
## Responding
|
||||||
|
A discord user will request access to the game. If the ACTIVITY_JOIN_REQUEST has been subscribed too, the ACTIVITY_JOIN_REQUEST event will be sent to the host's game. Accept it with following model:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nonce": "5dc0c062-98c6-47a0-8922-15aerg126",
|
||||||
|
"cmd": "SEND_ACTIVITY_JOIN_INVITE",
|
||||||
|
"args":
|
||||||
|
{
|
||||||
|
"user_id": "53908232506183680"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To reject the request, use `CLOSE_ACTIVITY_REQUEST`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nonce": "5dc0c062-98c6-47a0-8922-dasg256eafg",
|
||||||
|
"cmd": "CLOSE_ACTIVITY_REQUEST",
|
||||||
|
"args":
|
||||||
|
{
|
||||||
|
"user_id": "53908232506183680"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
Here are just some quick notes to help with some common troubleshooting problems.
|
||||||
|
* IPC will echo back every command you send as a response. Use this as a lock-step feature to avoid flooding messages. Can be used to validate messages such as the Presence or Subscribes.
|
||||||
|
* The pipe expects for frames to be written in a single byte array. You cannot do multiple `stream.Write(opcode);` `stream.Write(length);` as it will break the pipe. Instead create a buffer, write the data to the buffer, then send the entire buffer to the stream.
|
||||||
|
* Discord can be on any pipe ranging from `discord-ipc-0` to `discord-ipc-9`. It is a good idea to try and connect to each one and keeping the first one you connect too. For multiple clients (eg Discord and Canary), you might want to add a feature to manually select the pipe so you can more easily debug the application.
|
||||||
|
* All enums are `lower_snake_case`.
|
||||||
|
* The opcode and length in the header are `Little Endian Unsigned Integers (32bits)`. In some languages, you must convert them as they can be architecture specific.
|
||||||
|
* [Discord Rich Presence How-To](https://discordapp.com/developers/docs/rich-presence/how-to) contains a lot of the information this document doesn't. For example, it will tell you about the response payload.
|
||||||
|
* In the documentation, DISCORD_REPLY_IGNORE is just implemented the same as DISCORD_REPLY_NO.
|
||||||
|
* You can test the Join / Spectate feature by enabling them in your profile and whitelisting a test account. Use Canary to run 2 accounts on the same machine.
|
||||||
|
@ -11,7 +11,7 @@ public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent<DiscordRpc.
|
|||||||
|
|
||||||
public class DiscordController : MonoBehaviour
|
public class DiscordController : MonoBehaviour
|
||||||
{
|
{
|
||||||
public DiscordRpc.RichPresence presence;
|
public DiscordRpc.RichPresence presence = new DiscordRpc.RichPresence();
|
||||||
public string applicationId;
|
public string applicationId;
|
||||||
public string optionalSteamId;
|
public string optionalSteamId;
|
||||||
public int callbackCalls;
|
public int callbackCalls;
|
||||||
@ -33,7 +33,7 @@ public class DiscordController : MonoBehaviour
|
|||||||
|
|
||||||
presence.details = string.Format("Button clicked {0} times", clickCounter);
|
presence.details = string.Format("Button clicked {0} times", clickCounter);
|
||||||
|
|
||||||
DiscordRpc.UpdatePresence(ref presence);
|
DiscordRpc.UpdatePresence(presence);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RequestRespondYes()
|
public void RequestRespondYes()
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
public class DiscordRpc
|
public class DiscordRpc
|
||||||
{
|
{
|
||||||
@ -30,27 +33,27 @@ public class DiscordRpc
|
|||||||
public RequestCallback requestCallback;
|
public RequestCallback requestCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
[System.Serializable]
|
[Serializable, StructLayout(LayoutKind.Sequential)]
|
||||||
public struct RichPresence
|
public struct RichPresenceStruct
|
||||||
{
|
{
|
||||||
public string state; /* max 128 bytes */
|
public IntPtr state; /* max 128 bytes */
|
||||||
public string details; /* max 128 bytes */
|
public IntPtr details; /* max 128 bytes */
|
||||||
public long startTimestamp;
|
public long startTimestamp;
|
||||||
public long endTimestamp;
|
public long endTimestamp;
|
||||||
public string largeImageKey; /* max 32 bytes */
|
public IntPtr largeImageKey; /* max 32 bytes */
|
||||||
public string largeImageText; /* max 128 bytes */
|
public IntPtr largeImageText; /* max 128 bytes */
|
||||||
public string smallImageKey; /* max 32 bytes */
|
public IntPtr smallImageKey; /* max 32 bytes */
|
||||||
public string smallImageText; /* max 128 bytes */
|
public IntPtr smallImageText; /* max 128 bytes */
|
||||||
public string partyId; /* max 128 bytes */
|
public IntPtr partyId; /* max 128 bytes */
|
||||||
public int partySize;
|
public int partySize;
|
||||||
public int partyMax;
|
public int partyMax;
|
||||||
public string matchSecret; /* max 128 bytes */
|
public IntPtr matchSecret; /* max 128 bytes */
|
||||||
public string joinSecret; /* max 128 bytes */
|
public IntPtr joinSecret; /* max 128 bytes */
|
||||||
public string spectateSecret; /* max 128 bytes */
|
public IntPtr spectateSecret; /* max 128 bytes */
|
||||||
public bool instance;
|
public bool instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
[System.Serializable]
|
[Serializable]
|
||||||
public struct JoinRequest
|
public struct JoinRequest
|
||||||
{
|
{
|
||||||
public string userId;
|
public string userId;
|
||||||
@ -76,12 +79,142 @@ public class DiscordRpc
|
|||||||
public static extern void RunCallbacks();
|
public static extern void RunCallbacks();
|
||||||
|
|
||||||
[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)]
|
||||||
public static extern void UpdatePresence(ref RichPresence presence);
|
private static extern void UpdatePresenceNative(ref RichPresenceStruct presence);
|
||||||
|
|
||||||
[DllImport("discord-rpc", EntryPoint = "Discord_ClearPresence", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("discord-rpc", EntryPoint = "Discord_ClearPresence", CallingConvention = CallingConvention.Cdecl)]
|
||||||
public static extern void ClearPresence();
|
public static extern void ClearPresence();
|
||||||
|
|
||||||
[DllImport("discord-rpc", EntryPoint = "Discord_Respond", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("discord-rpc", EntryPoint = "Discord_Respond", CallingConvention = CallingConvention.Cdecl)]
|
||||||
public static extern void Respond(string userId, Reply reply);
|
public static extern void Respond(string userId, Reply reply);
|
||||||
}
|
|
||||||
|
|
||||||
|
[DllImport("discord-rpc", EntryPoint = "Discord_UpdateHandlers", CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
public static extern void UpdateHandlers(ref EventHandlers handlers);
|
||||||
|
|
||||||
|
public static void UpdatePresence(RichPresence presence)
|
||||||
|
{
|
||||||
|
var presencestruct = presence.GetStruct();
|
||||||
|
UpdatePresenceNative(ref presencestruct);
|
||||||
|
presence.FreeMem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RichPresence
|
||||||
|
{
|
||||||
|
private RichPresenceStruct _presence;
|
||||||
|
private readonly List<IntPtr> _buffers = new List<IntPtr>(10);
|
||||||
|
|
||||||
|
public string state; /* max 128 bytes */
|
||||||
|
public string details; /* max 128 bytes */
|
||||||
|
public long startTimestamp;
|
||||||
|
public long endTimestamp;
|
||||||
|
public string largeImageKey; /* max 32 bytes */
|
||||||
|
public string largeImageText; /* max 128 bytes */
|
||||||
|
public string smallImageKey; /* max 32 bytes */
|
||||||
|
public string smallImageText; /* max 128 bytes */
|
||||||
|
public string partyId; /* max 128 bytes */
|
||||||
|
public int partySize;
|
||||||
|
public int partyMax;
|
||||||
|
public string matchSecret; /* max 128 bytes */
|
||||||
|
public string joinSecret; /* max 128 bytes */
|
||||||
|
public string spectateSecret; /* max 128 bytes */
|
||||||
|
public bool instance;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the <see cref="RichPresenceStruct"/> reprensentation of this instance
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><see cref="RichPresenceStruct"/> reprensentation of this instance</returns>
|
||||||
|
internal RichPresenceStruct GetStruct()
|
||||||
|
{
|
||||||
|
if (_buffers.Count > 0)
|
||||||
|
{
|
||||||
|
FreeMem();
|
||||||
|
}
|
||||||
|
|
||||||
|
_presence.state = StrToPtr(state, 128);
|
||||||
|
_presence.details = StrToPtr(details, 128);
|
||||||
|
_presence.startTimestamp = startTimestamp;
|
||||||
|
_presence.endTimestamp = endTimestamp;
|
||||||
|
_presence.largeImageKey = StrToPtr(largeImageKey, 32);
|
||||||
|
_presence.largeImageText = StrToPtr(largeImageText, 128);
|
||||||
|
_presence.smallImageKey = StrToPtr(smallImageKey, 32);
|
||||||
|
_presence.smallImageText = StrToPtr(smallImageText, 128);
|
||||||
|
_presence.partyId = StrToPtr(partyId, 128);
|
||||||
|
_presence.partySize = partySize;
|
||||||
|
_presence.partyMax = partyMax;
|
||||||
|
_presence.matchSecret = StrToPtr(matchSecret, 128);
|
||||||
|
_presence.joinSecret = StrToPtr(joinSecret, 128);
|
||||||
|
_presence.spectateSecret = StrToPtr(spectateSecret, 128);
|
||||||
|
_presence.instance = instance;
|
||||||
|
|
||||||
|
return _presence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a pointer to a representation of the given string with a size of maxbytes
|
||||||
|
/// </summary>
|
||||||
|
/// <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>
|
||||||
|
private IntPtr StrToPtr(string input, int maxbytes)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(input)) return IntPtr.Zero;
|
||||||
|
var convstr = StrClampBytes(input, maxbytes);
|
||||||
|
var convbytecnt = Encoding.UTF8.GetByteCount(convstr);
|
||||||
|
var buffer = Marshal.AllocHGlobal(convbytecnt);
|
||||||
|
_buffers.Add(buffer);
|
||||||
|
Marshal.Copy(Encoding.UTF8.GetBytes(convstr), 0, buffer, convbytecnt);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert string to UTF-8 and add null termination
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="toconv">string to convert</param>
|
||||||
|
/// <returns>UTF-8 representation of <see cref="toconv"/> with added null termination</returns>
|
||||||
|
private static string StrToUtf8NullTerm(string toconv)
|
||||||
|
{
|
||||||
|
var str = toconv.Trim();
|
||||||
|
var bytes = Encoding.Default.GetBytes(str);
|
||||||
|
if (bytes.Length > 0 && bytes[bytes.Length - 1] != 0)
|
||||||
|
{
|
||||||
|
str += "\0\0";
|
||||||
|
}
|
||||||
|
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>
|
||||||
|
/// Free the allocated memory for conversion to <see cref="RichPresenceStruct"/>
|
||||||
|
/// </summary>
|
||||||
|
internal void FreeMem()
|
||||||
|
{
|
||||||
|
for (var i = _buffers.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(_buffers[i]);
|
||||||
|
_buffers.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,7 @@ public class ScriptBatch
|
|||||||
proc.StartInfo.EnvironmentVariables["PATH"] = newPath;
|
proc.StartInfo.EnvironmentVariables["PATH"] = newPath;
|
||||||
#endif
|
#endif
|
||||||
proc.StartInfo.FileName = "python";
|
proc.StartInfo.FileName = "python";
|
||||||
proc.StartInfo.Arguments = "build.py for_unity";
|
proc.StartInfo.Arguments = "build.py unity";
|
||||||
proc.StartInfo.WorkingDirectory = "../..";
|
proc.StartInfo.WorkingDirectory = "../..";
|
||||||
proc.Start();
|
proc.Start();
|
||||||
proc.WaitForExit();
|
proc.WaitForExit();
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
This is a simple example in C of using the rich presence API asyncronously.
|
This is a simple example in C of using the rich presence API asynchronously.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define _CRT_SECURE_NO_WARNINGS /* thanks Microsoft */
|
#define _CRT_SECURE_NO_WARNINGS /* thanks Microsoft */
|
||||||
@ -9,7 +9,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "discord-rpc.h"
|
#include "discord_rpc.h"
|
||||||
|
|
||||||
static const char* APPLICATION_ID = "345229890980937739";
|
static const char* APPLICATION_ID = "345229890980937739";
|
||||||
static int FrustrationLevel = 0;
|
static int FrustrationLevel = 0;
|
||||||
|
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
#include "DiscordRpcPrivatePCH.h"
|
#include "DiscordRpcPrivatePCH.h"
|
||||||
#include "DiscordRpcBlueprint.h"
|
#include "DiscordRpcBlueprint.h"
|
||||||
#include "discord-rpc.h"
|
#include "discord_rpc.h"
|
||||||
|
|
||||||
DEFINE_LOG_CATEGORY(Discord)
|
DEFINE_LOG_CATEGORY(Discord)
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ static void JoinRequestHandler(const DiscordJoinRequest* request)
|
|||||||
jr.username = ANSI_TO_TCHAR(request->username);
|
jr.username = ANSI_TO_TCHAR(request->username);
|
||||||
jr.discriminator = ANSI_TO_TCHAR(request->discriminator);
|
jr.discriminator = ANSI_TO_TCHAR(request->discriminator);
|
||||||
jr.avatar = ANSI_TO_TCHAR(request->avatar);
|
jr.avatar = ANSI_TO_TCHAR(request->avatar);
|
||||||
UE_LOG(Discord, Log, TEXT("Discord join request from %s#%s"), *jr.username, *jr.discriminator);
|
UE_LOG(Discord, Log, TEXT("Discord join request from %s - %s#%s"), *jr.userId, *jr.username, *jr.discriminator);
|
||||||
if (self) {
|
if (self) {
|
||||||
self->OnJoinRequest.Broadcast(jr);
|
self->OnJoinRequest.Broadcast(jr);
|
||||||
}
|
}
|
||||||
@ -134,7 +134,6 @@ void UDiscordRpc::UpdatePresence()
|
|||||||
|
|
||||||
auto spectateSecret = StringCast<ANSICHAR>(*RichPresence.spectateSecret);
|
auto spectateSecret = StringCast<ANSICHAR>(*RichPresence.spectateSecret);
|
||||||
rp.spectateSecret = spectateSecret.Get();
|
rp.spectateSecret = spectateSecret.Get();
|
||||||
|
|
||||||
rp.startTimestamp = RichPresence.startTimestamp;
|
rp.startTimestamp = RichPresence.startTimestamp;
|
||||||
rp.endTimestamp = RichPresence.endTimestamp;
|
rp.endTimestamp = RichPresence.endTimestamp;
|
||||||
rp.partySize = RichPresence.partySize;
|
rp.partySize = RichPresence.partySize;
|
||||||
@ -148,3 +147,10 @@ void UDiscordRpc::ClearPresence()
|
|||||||
{
|
{
|
||||||
Discord_ClearPresence();
|
Discord_ClearPresence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UDiscordRpc::Respond(const FString& userId, int reply)
|
||||||
|
{
|
||||||
|
UE_LOG(Discord, Log, TEXT("Responding %d to join request from %s"), reply, *userId);
|
||||||
|
FTCHARToUTF8 utf8_userid(*userId);
|
||||||
|
Discord_Respond(utf8_userid.Get(), reply);
|
||||||
|
}
|
||||||
|
@ -111,6 +111,11 @@ public:
|
|||||||
Category = "Discord")
|
Category = "Discord")
|
||||||
void ClearPresence();
|
void ClearPresence();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,
|
||||||
|
meta = (DisplayName = "Respond to join request", Keywords = "Discord rpc"),
|
||||||
|
Category = "Discord")
|
||||||
|
void Respond(const FString& userId, int reply);
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly,
|
UPROPERTY(BlueprintReadOnly,
|
||||||
meta = (DisplayName = "Is Discord connected", Keywords = "Discord rpc"),
|
meta = (DisplayName = "Is Discord connected", Keywords = "Discord rpc"),
|
||||||
Category = "Discord")
|
Category = "Discord")
|
||||||
|
26
include/discord_register.h
Normal file
26
include/discord_register.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(DISCORD_DYNAMIC_LIB)
|
||||||
|
# if defined(_WIN32)
|
||||||
|
# if defined(DISCORD_BUILDING_SDK)
|
||||||
|
# define DISCORD_EXPORT __declspec(dllexport)
|
||||||
|
# else
|
||||||
|
# define DISCORD_EXPORT __declspec(dllimport)
|
||||||
|
# endif
|
||||||
|
# else
|
||||||
|
# define DISCORD_EXPORT __attribute__((visibility("default")))
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
# define DISCORD_EXPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command);
|
||||||
|
DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
@ -80,6 +80,8 @@ DISCORD_EXPORT void Discord_ClearPresence(void);
|
|||||||
|
|
||||||
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
|
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
|
||||||
|
|
||||||
|
DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} /* extern "C" */
|
} /* extern "C" */
|
||||||
#endif
|
#endif
|
@ -6,9 +6,9 @@ option(USE_STATIC_CRT "Use /MT[d] for dynamic library" OFF)
|
|||||||
set(CMAKE_CXX_STANDARD 14)
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
|
|
||||||
set(BASE_RPC_SRC
|
set(BASE_RPC_SRC
|
||||||
${PROJECT_SOURCE_DIR}/include/discord-rpc.h
|
${PROJECT_SOURCE_DIR}/include/discord_rpc.h
|
||||||
discord-rpc.cpp
|
discord_rpc.cpp
|
||||||
discord_register.h
|
${PROJECT_SOURCE_DIR}/include/discord_register.h
|
||||||
rpc_connection.h
|
rpc_connection.h
|
||||||
rpc_connection.cpp
|
rpc_connection.cpp
|
||||||
serialization.h
|
serialization.h
|
||||||
@ -55,7 +55,7 @@ if(WIN32)
|
|||||||
/wd5027 # move assignment operator was implicitly defined as deleted
|
/wd5027 # move assignment operator was implicitly defined as deleted
|
||||||
)
|
)
|
||||||
endif(MSVC)
|
endif(MSVC)
|
||||||
target_link_libraries(discord-rpc PRIVATE psapi)
|
target_link_libraries(discord-rpc PRIVATE psapi advapi32)
|
||||||
endif(WIN32)
|
endif(WIN32)
|
||||||
|
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
@ -129,6 +129,7 @@ install(
|
|||||||
|
|
||||||
install(
|
install(
|
||||||
FILES
|
FILES
|
||||||
"../include/discord-rpc.h"
|
"../include/discord_rpc.h"
|
||||||
|
"../include/discord_register.h"
|
||||||
DESTINATION "include"
|
DESTINATION "include"
|
||||||
)
|
)
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
#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
|
|
@ -1,4 +1,5 @@
|
|||||||
#include "discord-rpc.h"
|
#include "discord_rpc.h"
|
||||||
|
#include "discord_register.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@ -8,7 +9,7 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
bool Mkdir(const char* path)
|
static bool Mkdir(const char* path)
|
||||||
{
|
{
|
||||||
int result = mkdir(path, 0755);
|
int result = mkdir(path, 0755);
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
@ -21,7 +22,7 @@ bool Mkdir(const char* path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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" DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command)
|
||||||
{
|
{
|
||||||
// Add a desktop file and update some mime handlers so that xdg-open does the right thing.
|
// Add a desktop file and update some mime handlers so that xdg-open does the right thing.
|
||||||
|
|
||||||
@ -90,7 +91,7 @@ extern "C" void Discord_Register(const char* applicationId, const char* command)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
|
extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, 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);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "discord-rpc.h"
|
#include "discord_rpc.h"
|
||||||
|
#include "discord_register.h"
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#define NOMCX
|
#define NOMCX
|
||||||
@ -7,7 +8,7 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <psapi.h>
|
#include <psapi.h>
|
||||||
#include <cwchar>
|
#include <cwchar>
|
||||||
#include <stdio.h>
|
#include <cstdio>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updated fixes for MinGW and WinXP
|
* Updated fixes for MinGW and WinXP
|
||||||
@ -20,11 +21,16 @@
|
|||||||
*/
|
*/
|
||||||
#ifdef __MINGW32__
|
#ifdef __MINGW32__
|
||||||
/// strsafe.h fixes
|
/// strsafe.h fixes
|
||||||
#define StringCbPrintfW snwprintf
|
static HRESULT StringCbPrintfW(LPWSTR pszDest, size_t cbDest, LPCWSTR pszFormat, ...)
|
||||||
LPWSTR StringCbCopyW(LPWSTR a, size_t l, LPCWSTR b)
|
|
||||||
{
|
{
|
||||||
a[l-1] = 0;
|
HRESULT ret;
|
||||||
return wcsncpy(a, b, l - 1); // does not set the last byte to 0 on overflow, so it's set to 0 above
|
va_list va;
|
||||||
|
va_start(va, pszFormat);
|
||||||
|
cbDest /= 2; // Size is divided by 2 to convert from bytes to wide characters - causes segfault othervise
|
||||||
|
ret = vsnwprintf(pszDest, cbDest, pszFormat, va);
|
||||||
|
pszDest[cbDest - 1] = 0; // Terminate the string in case a buffer overflow; -1 will be returned
|
||||||
|
va_end(va);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#include <strsafe.h>
|
#include <strsafe.h>
|
||||||
@ -38,21 +44,21 @@ LPWSTR StringCbCopyW(LPWSTR a, size_t l, LPCWSTR b)
|
|||||||
#undefine RegSetKeyValueW
|
#undefine RegSetKeyValueW
|
||||||
#endif
|
#endif
|
||||||
#define RegSetKeyValueW regset
|
#define RegSetKeyValueW regset
|
||||||
LSTATUS regset(HKEY hkey, LPCWSTR subkey, LPCWSTR name, DWORD type, const void *data, DWORD len)
|
static LSTATUS regset(HKEY hkey, LPCWSTR subkey, LPCWSTR name, DWORD type, const void *data, DWORD len)
|
||||||
{
|
{
|
||||||
HKEY hsubkey = NULL;
|
HKEY htkey = hkey, hsubkey = nullptr;
|
||||||
LSTATUS ret;
|
LSTATUS ret;
|
||||||
if (subkey && subkey[0]) /* need to create the subkey */
|
if (subkey && subkey[0])
|
||||||
{
|
{
|
||||||
if ((ret = RegCreateKeyW( hkey, subkey, &hsubkey )) != ERROR_SUCCESS) return ret;
|
if((ret = RegCreateKeyExW(hkey, subkey, 0, 0, 0, KEY_ALL_ACCESS, 0, &hsubkey, 0)) != ERROR_SUCCESS) return ret;
|
||||||
hkey = hsubkey;
|
htkey = hsubkey;
|
||||||
}
|
}
|
||||||
ret = RegSetValueExW( hkey, name, 0, type, (const BYTE*)data, len );
|
ret = RegSetValueExW(htkey, name, 0, type, (const BYTE*)data, len);
|
||||||
if (hsubkey) RegCloseKey( hsubkey );
|
if (hsubkey && hsubkey != hkey) RegCloseKey(hsubkey);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
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>://
|
||||||
@ -66,7 +72,8 @@ void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
wchar_t protocolName[64];
|
wchar_t protocolName[64];
|
||||||
@ -115,7 +122,7 @@ void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
|
|||||||
RegCloseKey(key);
|
RegCloseKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_Register(const char* applicationId, const char* command)
|
extern "C" DISCORD_EXPORT 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);
|
||||||
@ -131,7 +138,7 @@ 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)
|
extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, 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);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#include "discord-rpc.h"
|
#include "discord_rpc.h"
|
||||||
|
|
||||||
#include "backoff.h"
|
#include "backoff.h"
|
||||||
#include "discord_register.h"
|
#include "discord_register.h"
|
||||||
@ -60,6 +60,7 @@ static char LastErrorMessage[256];
|
|||||||
static int LastDisconnectErrorCode{0};
|
static int LastDisconnectErrorCode{0};
|
||||||
static char LastDisconnectErrorMessage[256];
|
static char LastDisconnectErrorMessage[256];
|
||||||
static std::mutex PresenceMutex;
|
static std::mutex PresenceMutex;
|
||||||
|
static std::mutex HandlerMutex;
|
||||||
static QueuedMessage QueuedPresence{};
|
static QueuedMessage QueuedPresence{};
|
||||||
static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
|
static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
|
||||||
static MsgQueue<JoinRequest, JoinQueueSize> JoinAskQueue;
|
static MsgQueue<JoinRequest, JoinQueueSize> JoinAskQueue;
|
||||||
@ -212,15 +213,15 @@ static void Discord_UpdateConnection(void)
|
|||||||
// writes
|
// writes
|
||||||
if (QueuedPresence.length) {
|
if (QueuedPresence.length) {
|
||||||
QueuedMessage local;
|
QueuedMessage local;
|
||||||
PresenceMutex.lock();
|
{
|
||||||
local.Copy(QueuedPresence);
|
std::lock_guard<std::mutex> guard(PresenceMutex);
|
||||||
QueuedPresence.length = 0;
|
local.Copy(QueuedPresence);
|
||||||
PresenceMutex.unlock();
|
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
|
||||||
PresenceMutex.lock();
|
std::lock_guard<std::mutex> guard(PresenceMutex);
|
||||||
QueuedPresence.Copy(local);
|
QueuedPresence.Copy(local);
|
||||||
PresenceMutex.unlock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,6 +251,19 @@ static bool RegisterForEvent(const char* evtName)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool DeregisterForEvent(const char* evtName)
|
||||||
|
{
|
||||||
|
auto qmessage = SendQueue.GetNextAddMessage();
|
||||||
|
if (qmessage) {
|
||||||
|
qmessage->length =
|
||||||
|
JsonWriteUnsubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
||||||
|
SendQueue.CommitAdd();
|
||||||
|
SignalIOActivity();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
||||||
DiscordEventHandlers* handlers,
|
DiscordEventHandlers* handlers,
|
||||||
int autoRegister,
|
int autoRegister,
|
||||||
@ -266,11 +280,14 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
|||||||
|
|
||||||
Pid = GetProcessId();
|
Pid = GetProcessId();
|
||||||
|
|
||||||
if (handlers) {
|
{
|
||||||
Handlers = *handlers;
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
}
|
if (handlers) {
|
||||||
else {
|
Handlers = *handlers;
|
||||||
Handlers = {};
|
}
|
||||||
|
else {
|
||||||
|
Handlers = {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Connection) {
|
if (Connection) {
|
||||||
@ -279,20 +296,9 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
|||||||
|
|
||||||
Connection = RpcConnection::Create(applicationId);
|
Connection = RpcConnection::Create(applicationId);
|
||||||
Connection->onConnect = []() {
|
Connection->onConnect = []() {
|
||||||
|
Discord_UpdateHandlers(&Handlers);
|
||||||
WasJustConnected.exchange(true);
|
WasJustConnected.exchange(true);
|
||||||
ReconnectTimeMs.reset();
|
ReconnectTimeMs.reset();
|
||||||
|
|
||||||
if (Handlers.joinGame) {
|
|
||||||
RegisterForEvent("ACTIVITY_JOIN");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Handlers.spectateGame) {
|
|
||||||
RegisterForEvent("ACTIVITY_SPECTATE");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Handlers.joinRequest) {
|
|
||||||
RegisterForEvent("ACTIVITY_JOIN_REQUEST");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Connection->onDisconnect = [](int err, const char* message) {
|
Connection->onDisconnect = [](int err, const char* message) {
|
||||||
LastDisconnectErrorCode = err;
|
LastDisconnectErrorCode = err;
|
||||||
@ -318,10 +324,11 @@ extern "C" DISCORD_EXPORT void Discord_Shutdown(void)
|
|||||||
|
|
||||||
extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence)
|
extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence)
|
||||||
{
|
{
|
||||||
PresenceMutex.lock();
|
{
|
||||||
QueuedPresence.length = JsonWriteRichPresenceObj(
|
std::lock_guard<std::mutex> guard(PresenceMutex);
|
||||||
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
|
QueuedPresence.length = JsonWriteRichPresenceObj(
|
||||||
PresenceMutex.unlock();
|
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
|
||||||
|
}
|
||||||
SignalIOActivity();
|
SignalIOActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,25 +367,38 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
|
|||||||
|
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
// if we are connected, disconnect cb first
|
// if we are connected, disconnect cb first
|
||||||
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
if (wasDisconnected && Handlers.disconnected) {
|
if (wasDisconnected && Handlers.disconnected) {
|
||||||
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WasJustConnected.exchange(false) && Handlers.ready) {
|
if (WasJustConnected.exchange(false)) {
|
||||||
Handlers.ready();
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
|
if (Handlers.ready) {
|
||||||
|
Handlers.ready();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GotErrorMessage.exchange(false) && Handlers.errored) {
|
if (GotErrorMessage.exchange(false)) {
|
||||||
Handlers.errored(LastErrorCode, LastErrorMessage);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
|
if (Handlers.errored) {
|
||||||
|
Handlers.errored(LastErrorCode, LastErrorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WasJoinGame.exchange(false) && Handlers.joinGame) {
|
if (WasJoinGame.exchange(false)) {
|
||||||
Handlers.joinGame(JoinGameSecret);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
|
if (Handlers.joinGame) {
|
||||||
|
Handlers.joinGame(JoinGameSecret);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WasSpectateGame.exchange(false) && Handlers.spectateGame) {
|
if (WasSpectateGame.exchange(false)) {
|
||||||
Handlers.spectateGame(SpectateGameSecret);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
|
if (Handlers.spectateGame) {
|
||||||
|
Handlers.spectateGame(SpectateGameSecret);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right now this batches up any requests and sends them all in a burst; I could imagine a world
|
// Right now this batches up any requests and sends them all in a burst; I could imagine a world
|
||||||
@ -388,17 +408,50 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
|
|||||||
// not it should be trivial for the implementer to make a queue themselves.
|
// not it should be trivial for the implementer to make a queue themselves.
|
||||||
while (JoinAskQueue.HavePendingSends()) {
|
while (JoinAskQueue.HavePendingSends()) {
|
||||||
auto req = JoinAskQueue.GetNextSendMessage();
|
auto req = JoinAskQueue.GetNextSendMessage();
|
||||||
if (Handlers.joinRequest) {
|
{
|
||||||
DiscordJoinRequest djr{req->userId, req->username, req->discriminator, req->avatar};
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
Handlers.joinRequest(&djr);
|
if (Handlers.joinRequest) {
|
||||||
|
DiscordJoinRequest djr{req->userId, req->username, req->discriminator, req->avatar};
|
||||||
|
Handlers.joinRequest(&djr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
JoinAskQueue.CommitSend();
|
JoinAskQueue.CommitSend();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
// if we are not connected, disconnect message last
|
// if we are not connected, disconnect message last
|
||||||
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
if (wasDisconnected && Handlers.disconnected) {
|
if (wasDisconnected && Handlers.disconnected) {
|
||||||
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* newHandlers)
|
||||||
|
{
|
||||||
|
if (newHandlers) {
|
||||||
|
|
||||||
|
#define HANDLE_EVENT_REGISTRATION(handler_name, event) \
|
||||||
|
if (!Handlers.handler_name && newHandlers->handler_name) { \
|
||||||
|
RegisterForEvent(event); \
|
||||||
|
} \
|
||||||
|
else if (Handlers.handler_name && !newHandlers->handler_name) { \
|
||||||
|
DeregisterForEvent(event); \
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
|
HANDLE_EVENT_REGISTRATION(joinGame, "ACTIVITY_JOIN")
|
||||||
|
HANDLE_EVENT_REGISTRATION(spectateGame, "ACTIVITY_SPECTATE")
|
||||||
|
HANDLE_EVENT_REGISTRATION(joinRequest, "ACTIVITY_JOIN_REQUEST")
|
||||||
|
|
||||||
|
#undef HANDLE_EVENT_REGISTRATION
|
||||||
|
|
||||||
|
Handlers = *newHandlers;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
|
Handlers = {};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
// outsmart GCC's missing-declarations warning
|
||||||
|
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID);
|
||||||
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID)
|
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID)
|
||||||
{
|
{
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#include "serialization.h"
|
#include "serialization.h"
|
||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
#include "discord-rpc.h"
|
#include "discord_rpc.h"
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void NumberToString(char* dest, T number)
|
void NumberToString(char* dest, T number)
|
||||||
@ -138,12 +138,10 @@ size_t JsonWriteRichPresenceObj(char* dest,
|
|||||||
presence->partyMax) {
|
presence->partyMax) {
|
||||||
WriteObject party(writer, "party");
|
WriteObject party(writer, "party");
|
||||||
WriteOptionalString(writer, "id", presence->partyId);
|
WriteOptionalString(writer, "id", presence->partyId);
|
||||||
if (presence->partySize) {
|
if (presence->partySize && presence->partyMax) {
|
||||||
WriteArray size(writer, "size");
|
WriteArray size(writer, "size");
|
||||||
writer.Int(presence->partySize);
|
writer.Int(presence->partySize);
|
||||||
if (0 < presence->partyMax) {
|
writer.Int(presence->partyMax);
|
||||||
writer.Int(presence->partyMax);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +197,25 @@ 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)
|
||||||
|
{
|
||||||
|
JsonWriter writer(dest, maxLen);
|
||||||
|
|
||||||
|
{
|
||||||
|
WriteObject obj(writer);
|
||||||
|
|
||||||
|
JsonWriteNonce(writer, nonce);
|
||||||
|
|
||||||
|
WriteKey(writer, "cmd");
|
||||||
|
writer.String("UNSUBSCRIBE");
|
||||||
|
|
||||||
|
WriteKey(writer, "evt");
|
||||||
|
writer.String(evtName);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, int reply, int nonce)
|
||||||
{
|
{
|
||||||
JsonWriter writer(dest, maxLen);
|
JsonWriter writer(dest, maxLen);
|
||||||
|
@ -47,6 +47,8 @@ size_t JsonWriteRichPresenceObj(char* dest,
|
|||||||
const DiscordRichPresence* presence);
|
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 JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce);
|
size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce);
|
||||||
|
|
||||||
// I want to use as few allocations as I can get away with, and to do that with RapidJson, you need
|
// I want to use as few allocations as I can get away with, and to do that with RapidJson, you need
|
||||||
|
Reference in New Issue
Block a user