61 Commits

Author SHA1 Message Date
8bb85f0545 Fix double function declaration 2019-01-14 00:09:17 -08:00
766596722c This one actually works!!!! 2019-01-04 14:13:30 -08:00
7fe88765fd maybe this fixes it 2019-01-04 14:11:37 -08:00
1d30b94987 Oops all variables 2019-01-04 14:11:37 -08:00
6796d2ffa9 Fix windows connection 2019-01-04 14:11:37 -08:00
d90a8efd47 Warnings as errors yelling 2019-01-04 14:11:37 -08:00
544f91a5a8 UE4 support 2019-01-04 14:11:37 -08:00
2f52c24f6d Get pipe from base connection instance 2019-01-04 14:11:09 -08:00
d5a342c7bb Choose pipe number on initialize 2019-01-04 14:10:56 -08:00
4e53fa0392 Fix code signing for macOS 10.10 (#260)
* Fix code signing for macOS 10.10
- Fixes #259

* Maybe this works
2018-12-19 08:40:51 -08:00
d478ed5608 Moved UE setup to be with unity setup (#254) 2018-12-14 16:04:31 -08:00
8db649ba5f Static EventHandler for IL2CPP support (#258)
* Added staticEventHandler which forward the invoke to the actual eventHandler set though Initialize

* Code Formatting - Replaced abs with 4 white spaces
2018-12-14 16:00:47 -08:00
e6390c8c41 Unity il2cpp support (#249)
* Initial il2cpp support attempts

* Fix crashes

* Different variable name

* Fix indenting

* Change back unneeded stuff
- callbackCalls didnt seem to do anything
2018-11-27 09:19:14 -08:00
2fec0b6dec Wrong name for macOS DLL file for Unity 2018-11-26 09:03:44 -08:00
dd47c7c66d Improve handling of disconnects and reconnects (#228)
* Check response 0 on disconnect

From recv(): The return value will be 0 when the peer has performed an orderly shutdown

* Add persistent presence and handlers

* Use buffer instead of raw struct

* Clear presence data on shutdown

* Remove CurrentPresence and add boolean instead

This removes the need for having 2 big buffers in favor of using a small boolean
2018-11-16 10:41:49 -08:00
98855b4d84 Fix #210: WARNINGS_AS_ERRORS doesn't work (#211)
#210 was created under assumption `build.py` is actually used, but upon
inspecting `.travis.yml` it turns out it is not.
2018-11-06 14:55:10 -08:00
ac2d064cb0 Flatten the condition to get rid of empty branch (#247) 2018-11-06 14:48:59 -08:00
d63ed30966 Fix typo in readme. (#245)
* fix typo in readme

* revert accidental quote change
2018-10-23 15:13:36 -07:00
7716eadca3 Update D binding link (#234)
DerelictDiscordRPC has been abandoned
and superseded by Discord RPC D.
2018-09-17 17:26:28 -07:00
e32d001809 readme nits 2018-08-17 05:19:17 -07:00
2cb9813eb6 Unity specific DLL setup 2018-08-17 05:17:57 -07:00
af380116a0 Check C# strings against UTF8 bytes instead of clamping (#221) 2018-08-16 11:23:28 -07:00
3d3ae7129d Fix mismatched signs in comparison after b44defe (#209)
```
../src/discord_register_linux.cpp: In function ‘void Discord_Register(const char*, const char*)’:
../src/discord_register_linux.cpp:37:31: warning: comparison of integer expressions of different signedness: ‘ssize_t’ {aka ‘long int’} and ‘long unsigned int’ [-Wsign-compare]
         if (size <= 0 || size >= sizeof(exePath)) {
                          ~~~~~^~~~~~~~~~~~~~~~~~
```
2018-07-30 12:50:49 -07:00
b44defe60a [Linux] Null-terminate output from readlink in Discord_Register. (#208)
* Explicitly null-terminate the output from readlink in discord_register_linux.cpp.

* The return value of readlink is a signed size_t.
2018-07-27 10:04:04 -07:00
dfad394be0 Added enum for response codes (#207)
Makes it easier to respond to a join request via Blueprints, without having to look it up in the docs.
Optional and fully backwards compatible.
2018-07-25 13:23:34 -07:00
a3ad6afee2 Added Discord RPC C# implementation to list. (#205)
Added my library to the list which is a implementation written entirely in C#
2018-07-09 12:04:59 -07:00
7c41a8ec19 Fixed issue with Discord RPC not updating presence during shutdown (#189) 2018-06-07 16:10:40 -07:00
5df1c5ae6d copy the whole folder UE4 2018-05-30 09:40:39 -07:00
c05c7148dd Updated README with UE plugin instructions (#183)
* Updated README with UE plugin instructions

Hopefully this will save some time when others want to implement this into their own UE Projects.

* Update README.md

* cleanup UE4 plugin help
2018-05-29 13:51:39 -07:00
ba9fe00c4d Dynamically create IoThread... (#179)
So that it doesn't get deleted before Discord is destroyed.
2018-05-16 13:21:16 -07:00
cac0362377 don't rely on unset env vars 2018-05-14 10:05:21 -07:00
7e0480e2ef Apply formatting (#178) 2018-05-14 09:25:17 -07:00
566076e3d8 add WARNINGS_AS_ERRORS cmake option (#176) 2018-05-10 17:46:11 -07:00
aa02012c14 alphabetize libs 2018-05-04 15:13:16 -07:00
f80bd72d22 Include pypresence library (#167) 2018-05-04 15:12:24 -07:00
acf7d6a054 Add link to lua-discordRPC (LuaJIT bindings) (#171) 2018-05-03 15:33:49 -07:00
1129c2ce4f Add link to DerelictDiscordRPC (D binding) (#169)
* Add link to DerelictDiscordRPC (D binding)

* Fix alphabetic order of community-wrappers
2018-04-23 11:20:47 -07:00
64027b336f Adding user object to READY event (#159)
* Pass the READY event data down in onConnect

* Changes made for UE4 and Unity wrappers

* Changing object name from joinRequest to DiscordUser
2018-04-16 10:25:44 -07:00
2ce9fe068b Syntax change to avoid gcc 4.8 segfaulting (#162) 2018-04-04 10:00:24 -07:00
be8a8e9380 ACTUALLY register the handlers on init 2018-03-29 14:33:46 -07:00
c70acbe7d1 Fix Unity buildhelper for linux
- Fixes #157
2018-03-26 10:56:05 -07:00
d97e6b48ed Note to install cmake
- Fixes #149
2018-03-26 10:37:03 -07:00
087282cd4b Dynamic Event Handler Registration (#135)
- Discord_RegisterHandlers() exported
- C# wrapper updated
- Dynamically sub/unsub to events
- Better mutex locking, for safety!
2018-03-23 10:25:28 -07:00
7e5d57e6fd Update cert to use new name (#158) 2018-03-23 10:18:46 -07:00
f3bd411b99 Update README.md 2018-03-19 10:29:11 -07:00
8e0c7848a6 Added more hard mode documentation (#148)
* ACTIVITY_JOIN_REQUEST does not have a secret

There is no secret passed to a Join Request

* Added how to respond

* Update hard-mode.md

* Added some tips

Added some helpful tips. Plan to add more as I go along.
2018-03-19 10:27:29 -07:00
e7f9396807 Fix a typo in send-presence.c (#144) 2018-03-13 16:58:14 -07:00
ad0b844672 Changed CMAKE_SOURCE_DIR to CMAKE_CURRENT_SOURCE_DIR (#143)
Helps with submodule implementations
2018-03-13 13:13:38 -07:00
d279c24c6a Add advapi32 to linked libraries (#140)
Required by `RegCreateKeyExW` and others.
2018-03-13 13:00:47 -07:00
d9caf72e9a Add missing timestamps in UE4 example
Fixes #137
2018-03-06 09:51:31 -08:00
e8091f5137 Changing kebab case filenames in source to snake case for consistency 2018-02-27 13:33:00 -08:00
4055565147 Update C# wrapper with visual C# compatible version (#126)
- Custom serializer to fix utf-8 strings in C#
2018-02-22 11:47:18 -08:00
578eb6de7c Provide fake DllMain declaration to fix missing-declarations warning (#130) 2018-02-15 14:36:31 -08:00
4e61b9c82c Fix mingw compilation with -Werror=missing-declarations (#128) 2018-02-14 13:33:02 -08:00
8ec10dc011 Fix compilation with -Werror=missing-declarations (#127) 2018-02-14 11:42:29 -08:00
f5f2d69a72 Update Unreal Example to include Ask to Join (#125) 2018-02-12 13:47:38 -08:00
453222075b partyMax is mandatory if partySize is included (#122) 2018-02-12 13:44:49 -08:00
c4201806cf Update build.py to properly build and copy libraries for Unity and Unreal (#120) 2018-02-12 13:40:41 -08:00
ccf04d21f5 Moving buildhelper to editor folder (#118) 2018-02-02 16:02:11 -08:00
c7b4e6b2fc Fix segfaults in Discord_RegisterW in MinGW builds (#105)
* Add MinGW and WinXP support, remove Win SDK dep when using MinGW

* Remove Win SDK dependency when compiled with MinGW

* Remap the Win SDK-depended functions to string.h substitutes

* Remap missing WinAPI call RegSetKeyValueW to a substitute function

* Remove warnings by pragma when using MinGW

* Fix segfaults in Discord_RegisterW in MinGW builds
2018-02-02 09:40:33 -08:00
eee5085e9b Exported Discord_Register and Discord_RegisterSteam (#109)
* Update CMakeLists.txt

* Update and rename src/discord_register.h to include/discord_register.h

* Update CMakeLists.txt

* Update discord_register_win.cpp

* Update discord_register_linux.cpp
2018-02-01 16:42:17 -08:00
31 changed files with 812 additions and 314 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/.vscode/ /.vscode/
/thirdparty/ /thirdparty/
.vs/ .vs/
.DS_Store

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 --config Release .. - cmake -DCLANG_FORMAT_SUFFIX=$CLANG_FORMAT_SUFFIX -DWARNINGS_AS_ERRORS=On --config Release ..
- cmake --build . -- -j2 - cmake --build . -- -j2

View File

@ -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})

111
README.md
View File

@ -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
@ -15,13 +15,76 @@ 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.
### From repo ### From repo
First-eth, you'll want `CMake`. There's a few different ways to install it on your system, and you should refer to [their website](https://cmake.org/install/). Many package managers provide ways of installing CMake as well.
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
@ -29,27 +92,29 @@ 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
@ -57,11 +122,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
@ -73,11 +138,15 @@ 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.jar](https://github.com/Vatuu/discord-rpc "Discord-RPC.jar") | Java | | [Discord RPC C#](https://github.com/Lachee/discord-rpc-csharp) | C# |
| [java-discord-rpc](https://github.com/MinnDevelopment/java-discord-rpc) | Java | | [Discord RPC D](https://github.com/voidblaster/discord-rpc-d) | [D](https://dlang.org/) |
| [Discord-IPC](https://github.com/jagrosh/DiscordIPC) | Java | | [discord-rpc.jar](https://github.com/Vatuu/discord-rpc 'Discord-RPC.jar') | Java |
| [Discord Rich Presence](https://npmjs.org/discord-rich-presence) | JavaScript | | [java-discord-rpc](https://github.com/MinnDevelopment/java-discord-rpc) | Java |
| [drpc4k](https://github.com/Bluexin/drpc4k) | [Kotlin](https://kotlinlang.org/) | | [Discord-IPC](https://github.com/jagrosh/DiscordIPC) | Java |
| [SwordRPC](https://github.com/Azoy/SwordRPC) | [Swift](https://swift.org) | | [Discord Rich Presence](https://npmjs.org/discord-rich-presence) | JavaScript |
| [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) |

141
build.py
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 = os.environ['WindowsSdkDir'] sdk_dir = 'c:\\Program Files (x86)\\Windows Kits\\10' # 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'
@ -62,59 +62,100 @@ def cli(ctx, clean):
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
ctx.invoke(libs, clean=clean) ctx.invoke(libs, clean=clean)
if IS_BUILD_MACHINE: if IS_BUILD_MACHINE:
ctx.invoke(sign) ctx.invoke(sign)
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, clean=False, static=False, shared=True, skip_formatter=True, just_release=True)
libs, BUILDS = []
clean=False,
static=False, click.echo('--- Copying libs and header into unity example')
shared=True, UNITY_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'button-clicker', 'Assets', 'Plugins')
skip_formatter=True,
just_release=True 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()
@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( ctx.invoke(libs, clean=False, static=False, shared=True, skip_formatter=True, just_release=True)
libs, BUILDS = []
clean=False,
static=False,
shared=True,
skip_formatter=True,
just_release=True
)
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):
@ -124,11 +165,7 @@ 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 = [ initial_cmake = ['cmake', SCRIPT_PATH, '-DCMAKE_INSTALL_PREFIX=%s' % os.path.join('..', 'install', build_name)]
'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:
@ -170,27 +207,33 @@ 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',
'/td', 'sha256', '/td',
'/fd', 'sha256', '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', os.path.expanduser('~/Library/Keychains/login.keychain'), '--keychain',
os.path.expanduser('~/Library/Keychains/login.keychain'),
'-vvvv', '-vvvv',
'--deep', '--deep',
'--force', '--force',
'--sign', 'Developer ID Application: Hammer & Chisel Inc. (53Q6R32WPB)', '--sign',
'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')
return return
click.echo('--- Signing') click.echo('--- Signing')
for path, _, filenames in os.walk(INSTALL_ROOT): for path, _, filenames in os.walk(INSTALL_ROOT):
for fname in filenames: for fname in filenames:
@ -232,6 +275,8 @@ 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

@ -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.

View File

@ -7,16 +7,15 @@ 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.JoinRequest> { } public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent<DiscordRpc.DiscordUser> { }
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 clickCounter; public int clickCounter;
public DiscordRpc.JoinRequest joinRequest; public DiscordRpc.DiscordUser 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;
@ -33,7 +32,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()
@ -50,43 +49,37 @@ public class DiscordController : MonoBehaviour
hasResponded.Invoke(); hasResponded.Invoke();
} }
public void ReadyCallback() public void ReadyCallback(ref DiscordRpc.DiscordUser connectedUser)
{ {
++callbackCalls; Debug.Log(string.Format("Discord: connected to {0}#{1}: {2}", connectedUser.username, connectedUser.discriminator, connectedUser.userId));
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.JoinRequest request) public void RequestCallback(ref DiscordRpc.DiscordUser 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);
@ -104,10 +97,8 @@ 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

@ -1,38 +1,131 @@
using System.Runtime.InteropServices; using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using AOT;
public class DiscordRpc public class DiscordRpc
{ {
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [MonoPInvokeCallback(typeof(OnReadyInfo))]
public delegate void ReadyCallback(); public static void ReadyCallback(ref DiscordUser connectedUser) { Callbacks.readyCallback(ref connectedUser); }
public delegate void OnReadyInfo(ref DiscordUser connectedUser);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [MonoPInvokeCallback(typeof(OnDisconnectedInfo))]
public delegate void DisconnectedCallback(int errorCode, string message); public static void DisconnectedCallback(int errorCode, string message) { Callbacks.disconnectedCallback(errorCode, message); }
public delegate void OnDisconnectedInfo(int errorCode, string message);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [MonoPInvokeCallback(typeof(OnErrorInfo))]
public delegate void ErrorCallback(int errorCode, string message); public static void ErrorCallback(int errorCode, string message) { Callbacks.errorCallback(errorCode, message); }
public delegate void OnErrorInfo(int errorCode, string message);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [MonoPInvokeCallback(typeof(OnJoinInfo))]
public delegate void JoinCallback(string secret); public static void JoinCallback(string secret) { Callbacks.joinCallback(secret); }
public delegate void OnJoinInfo(string secret);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [MonoPInvokeCallback(typeof(OnSpectateInfo))]
public delegate void SpectateCallback(string secret); public static void SpectateCallback(string secret) { Callbacks.spectateCallback(secret); }
public delegate void OnSpectateInfo(string secret);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [MonoPInvokeCallback(typeof(OnRequestInfo))]
public delegate void RequestCallback(ref JoinRequest request); public static void RequestCallback(ref DiscordUser request) { Callbacks.requestCallback(ref request); }
public delegate void OnRequestInfo(ref DiscordUser request);
static EventHandlers Callbacks { get; set; }
public struct EventHandlers public struct EventHandlers
{ {
public ReadyCallback readyCallback; public OnReadyInfo readyCallback;
public DisconnectedCallback disconnectedCallback; public OnDisconnectedInfo disconnectedCallback;
public ErrorCallback errorCallback; public OnErrorInfo errorCallback;
public JoinCallback joinCallback; public OnJoinInfo joinCallback;
public SpectateCallback spectateCallback; public OnSpectateInfo spectateCallback;
public RequestCallback requestCallback; public OnRequestInfo requestCallback;
} }
[System.Serializable] [Serializable, StructLayout(LayoutKind.Sequential)]
public struct RichPresence public struct RichPresenceStruct
{ {
public IntPtr state; /* max 128 bytes */
public IntPtr details; /* max 128 bytes */
public long startTimestamp;
public long endTimestamp;
public IntPtr largeImageKey; /* max 32 bytes */
public IntPtr largeImageText; /* max 128 bytes */
public IntPtr smallImageKey; /* max 32 bytes */
public IntPtr smallImageText; /* max 128 bytes */
public IntPtr partyId; /* max 128 bytes */
public int partySize;
public int partyMax;
public IntPtr matchSecret; /* max 128 bytes */
public IntPtr joinSecret; /* max 128 bytes */
public IntPtr spectateSecret; /* max 128 bytes */
public bool instance;
}
[Serializable]
public struct DiscordUser
{
public string userId;
public string username;
public string discriminator;
public string avatar;
}
public enum Reply
{
No = 0,
Yes = 1,
Ignore = 2
}
public static void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId, int pipe = 0)
{
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, pipe);
}
[DllImport("discord-rpc", EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)]
static extern void InitializeInternal(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId, int pipe);
[DllImport("discord-rpc", EntryPoint = "Discord_Shutdown", CallingConvention = CallingConvention.Cdecl)]
public static extern void Shutdown();
[DllImport("discord-rpc", EntryPoint = "Discord_RunCallbacks", CallingConvention = CallingConvention.Cdecl)]
public static extern void RunCallbacks();
[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)]
private static extern void UpdatePresenceNative(ref RichPresenceStruct presence);
[DllImport("discord-rpc", EntryPoint = "Discord_ClearPresence", CallingConvention = CallingConvention.Cdecl)]
public static extern void ClearPresence();
[DllImport("discord-rpc", EntryPoint = "Discord_Respond", CallingConvention = CallingConvention.Cdecl)]
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 state; /* max 128 bytes */
public string details; /* max 128 bytes */ public string details; /* max 128 bytes */
public long startTimestamp; public long startTimestamp;
@ -48,40 +141,82 @@ public class DiscordRpc
public string joinSecret; /* max 128 bytes */ public string joinSecret; /* max 128 bytes */
public string spectateSecret; /* max 128 bytes */ public string spectateSecret; /* max 128 bytes */
public bool instance; 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);
_presence.details = StrToPtr(details);
_presence.startTimestamp = startTimestamp;
_presence.endTimestamp = endTimestamp;
_presence.largeImageKey = StrToPtr(largeImageKey);
_presence.largeImageText = StrToPtr(largeImageText);
_presence.smallImageKey = StrToPtr(smallImageKey);
_presence.smallImageText = StrToPtr(smallImageText);
_presence.partyId = StrToPtr(partyId);
_presence.partySize = partySize;
_presence.partyMax = partyMax;
_presence.matchSecret = StrToPtr(matchSecret);
_presence.joinSecret = StrToPtr(joinSecret);
_presence.spectateSecret = StrToPtr(spectateSecret);
_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>
/// <returns>Pointer to the UTF-8 representation of <see cref="input"/></returns>
private IntPtr StrToPtr(string input)
{
if (string.IsNullOrEmpty(input)) return IntPtr.Zero;
var convbytecnt = Encoding.UTF8.GetByteCount(input);
var buffer = Marshal.AllocHGlobal(convbytecnt + 1);
for (int i = 0; i < convbytecnt + 1; i++)
{
Marshal.WriteByte(buffer, i, 0);
}
_buffers.Add(buffer);
Marshal.Copy(Encoding.UTF8.GetBytes(input), 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>
/// 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);
}
}
} }
}
[System.Serializable]
public struct JoinRequest
{
public string userId;
public string username;
public string discriminator;
public string avatar;
}
public enum Reply
{
No = 0,
Yes = 1,
Ignore = 2
}
[DllImport("discord-rpc", EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)]
public static extern void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId);
[DllImport("discord-rpc", EntryPoint = "Discord_Shutdown", CallingConvention = CallingConvention.Cdecl)]
public static extern void Shutdown();
[DllImport("discord-rpc", EntryPoint = "Discord_RunCallbacks", CallingConvention = CallingConvention.Cdecl)]
public static extern void RunCallbacks();
[DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)]
public static extern void UpdatePresence(ref RichPresence presence);
[DllImport("discord-rpc", EntryPoint = "Discord_ClearPresence", CallingConvention = CallingConvention.Cdecl)]
public static extern void ClearPresence();
[DllImport("discord-rpc", EntryPoint = "Discord_Respond", CallingConvention = CallingConvention.Cdecl)]
public static extern void Respond(string userId, Reply reply);
}

View File

@ -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();
@ -46,8 +46,8 @@ public class ScriptBatch
string[] srcDlls = { "../../builds/install/osx-dynamic/lib/libdiscord-rpc.dylib" }; string[] srcDlls = { "../../builds/install/osx-dynamic/lib/libdiscord-rpc.dylib" };
#else #else
string[] dstDirs = { "Assets/Plugins", "Assets/Plugins/x86", "Assets/Plugins/x86_64" }; string[] dstDirs = { "Assets/Plugins", "Assets/Plugins/x86", "Assets/Plugins/x86_64" };
string[] dstDlls = { "Assets/Plugins/x86/discord-rpc.so", "Assets/Plugins/x86_64/discord-rpc.so" }; string[] dstDlls = { "Assets/Plugins/discord-rpc.so" };
string[] srcDlls = { "../../builds/install/linux-dynamic/bin/discord-rpc.dll", "../../builds/install/win64-dynamic/bin/discord-rpc.dll" }; string[] srcDlls = { "../../builds/install/linux-dynamic/lib/libdiscord-rpc.so" };
#endif #endif
Debug.Assert(dstDlls.Length == srcDlls.Length); Debug.Assert(dstDlls.Length == srcDlls.Length);

View File

@ -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;
@ -52,14 +52,18 @@ static void updateDiscordPresence()
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(void) static void handleDiscordReady(const DiscordUser* connectedUser)
{ {
printf("\nDiscord: ready\n"); printf("\nDiscord: connected to user %s#%s - %s\n",
connectedUser->username,
connectedUser->discriminator,
connectedUser->userId);
} }
static void handleDiscordDisconnected(int errcode, const char* message) static void handleDiscordDisconnected(int errcode, const char* message)
@ -82,13 +86,13 @@ static void handleDiscordSpectate(const char* secret)
printf("\nDiscord: spectate (%s)\n", secret); printf("\nDiscord: spectate (%s)\n", secret);
} }
static void handleDiscordJoinRequest(const DiscordJoinRequest* request) static void handleDiscordJoinRequest(const DiscordUser* 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->avatar, request->discriminator,
request->userId); request->userId);
do { do {
printf("Accept? (y/n)"); printf("Accept? (y/n)");
@ -125,7 +129,7 @@ static void discordInit()
handlers.joinGame = handleDiscordJoin; handlers.joinGame = handleDiscordJoin;
handlers.spectateGame = handleDiscordSpectate; handlers.spectateGame = handleDiscordSpectate;
handlers.joinRequest = handleDiscordJoinRequest; handlers.joinRequest = handleDiscordJoinRequest;
Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL); Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL, 0);
} }
static void gameLoop() static void gameLoop()
@ -152,7 +156,8 @@ 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,19 +12,26 @@ 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 = FPaths::Combine(*BaseDir, TEXT("Source"), TEXT("ThirdParty"), TEXT("DiscordRpcLibrary")); const FString SDKDir =
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(EAppMsgType::Ok, LOCTEXT(LOCTEXT_NAMESPACE, "Failed to load DiscordRpc plugin. Plug-in will not be functional.")); FMessageDialog::Open(
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(EAppMsgType::Ok, LOCTEXT(LOCTEXT_NAMESPACE, "Failed to load DiscordRpc plugin. Plug-in will not be functional.")); FMessageDialog::Open(
EAppMsgType::Ok,
LOCTEXT(LOCTEXT_NAMESPACE,
"Failed to load DiscordRpc plugin. Plug-in will not be functional."));
FreeDependency(DiscordRpcLibraryHandle); FreeDependency(DiscordRpcLibraryHandle);
} }
#endif #endif
@ -49,8 +56,7 @@ 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;
} }
@ -59,8 +65,7 @@ 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

@ -1,17 +1,27 @@
#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)
static UDiscordRpc* self = nullptr; static UDiscordRpc* self = nullptr;
static void ReadyHandler() static void ReadyHandler(const DiscordUser* connectedUser)
{ {
UE_LOG(Discord, Log, TEXT("Discord connected")); FDiscordUserData ud;
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(); self->OnConnected.Broadcast(ud);
} }
} }
@ -52,22 +62,28 @@ static void SpectateGameHandler(const char* spectateSecret)
} }
} }
static void JoinRequestHandler(const DiscordJoinRequest* request) static void JoinRequestHandler(const DiscordUser* request)
{ {
FDiscordJoinRequestData jr; FDiscordUserData ud;
jr.userId = ANSI_TO_TCHAR(request->userId); ud.userId = ANSI_TO_TCHAR(request->userId);
jr.username = ANSI_TO_TCHAR(request->username); ud.username = ANSI_TO_TCHAR(request->username);
jr.discriminator = ANSI_TO_TCHAR(request->discriminator); ud.discriminator = ANSI_TO_TCHAR(request->discriminator);
jr.avatar = ANSI_TO_TCHAR(request->avatar); ud.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"),
*ud.userId,
*ud.username,
*ud.discriminator);
if (self) { if (self) {
self->OnJoinRequest.Broadcast(jr); self->OnJoinRequest.Broadcast(ud);
} }
} }
void UDiscordRpc::Initialize(const FString& applicationId, void UDiscordRpc::Initialize(const FString& applicationId,
bool autoRegister, bool autoRegister,
const FString& optionalSteamId) const FString& optionalSteamId,
int pipe)
{ {
self = this; self = this;
IsConnected = false; IsConnected = false;
@ -87,7 +103,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(), pipe);
} }
void UDiscordRpc::Shutdown() void UDiscordRpc::Shutdown()
@ -134,7 +150,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 +163,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);
}

View File

@ -11,7 +11,7 @@
* Ask to join callback data * Ask to join callback data
*/ */
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct FDiscordJoinRequestData { struct FDiscordUserData {
GENERATED_USTRUCT_BODY() GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
@ -24,15 +24,25 @@ struct FDiscordJoinRequestData {
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")
};
DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All); DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDiscordConnected); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordConnected, const FDiscordUserData&, joinRequest);
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 FDiscordJoinRequestData&, joinRequest); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoinRequest, const FDiscordUserData&, joinRequest);
// clang-format on // clang-format on
@ -89,7 +99,8 @@ public:
Category = "Discord") Category = "Discord")
void Initialize(const FString& applicationId, void Initialize(const FString& applicationId,
bool autoRegister, bool autoRegister,
const FString& optionalSteamId); const FString& optionalSteamId,
int optionalPipeNumber);
UFUNCTION(BlueprintCallable, UFUNCTION(BlueprintCallable,
meta = (DisplayName = "Shut down connection", Keywords = "Discord rpc"), meta = (DisplayName = "Shut down connection", Keywords = "Discord rpc"),
@ -111,6 +122,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")

View 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

View File

@ -41,20 +41,20 @@ typedef struct DiscordRichPresence {
int8_t instance; int8_t instance;
} DiscordRichPresence; } DiscordRichPresence;
typedef struct DiscordJoinRequest { typedef struct DiscordUser {
const char* userId; const char* userId;
const char* username; const char* username;
const char* discriminator; const char* discriminator;
const char* avatar; const char* avatar;
} DiscordJoinRequest; } DiscordUser;
typedef struct DiscordEventHandlers { typedef struct DiscordEventHandlers {
void (*ready)(void); void (*ready)(const DiscordUser* request);
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 DiscordJoinRequest* request); void (*joinRequest)(const DiscordUser* request);
} DiscordEventHandlers; } DiscordEventHandlers;
#define DISCORD_REPLY_NO 0 #define DISCORD_REPLY_NO 0
@ -64,7 +64,8 @@ typedef struct DiscordEventHandlers {
DISCORD_EXPORT void Discord_Initialize(const char* applicationId, DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers, DiscordEventHandlers* handlers,
int autoRegister, int autoRegister,
const char* optionalSteamId); const char* optionalSteamId,
int optionalPipeNumber);
DISCORD_EXPORT void Discord_Shutdown(void); DISCORD_EXPORT void Discord_Shutdown(void);
/* checks for incoming messages, dispatches callbacks */ /* checks for incoming messages, dispatches callbacks */
@ -80,6 +81,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

View File

@ -2,13 +2,14 @@ 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)
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 +56,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)
@ -71,12 +72,23 @@ 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
@ -129,6 +141,7 @@ install(
install( install(
FILES FILES
"../include/discord-rpc.h" "../include/discord_rpc.h"
"../include/discord_register.h"
DESTINATION "include" DESTINATION "include"
) )

View File

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

View File

@ -49,7 +49,7 @@ static const char* GetTempPath()
c = nullptr; c = nullptr;
} }
bool BaseConnection::Open() bool BaseConnection::Open(int pipe)
{ {
const char* tempPath = GetTempPath(); const char* tempPath = GetTempPath();
auto self = reinterpret_cast<BaseConnectionUnix*>(this); auto self = reinterpret_cast<BaseConnectionUnix*>(this);
@ -62,8 +62,7 @@ bool BaseConnection::Open()
int optval = 1; int optval = 1;
setsockopt(self->sock, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)); setsockopt(self->sock, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval));
#endif #endif
for (int pipeNum = pipe; pipeNum < 10; ++pipeNum) {
for (int pipeNum = 0; pipeNum < 10; ++pipeNum) {
snprintf( snprintf(
PipeAddr.sun_path, sizeof(PipeAddr.sun_path), "%s/discord-ipc-%d", tempPath, pipeNum); PipeAddr.sun_path, sizeof(PipeAddr.sun_path), "%s/discord-ipc-%d", tempPath, pipeNum);
int err = connect(self->sock, (const sockaddr*)&PipeAddr, sizeof(PipeAddr)); int err = connect(self->sock, (const sockaddr*)&PipeAddr, sizeof(PipeAddr));
@ -118,5 +117,8 @@ 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

@ -6,6 +6,7 @@
#define NOIME #define NOIME
#include <assert.h> #include <assert.h>
#include <windows.h> #include <windows.h>
#include <sstream>
int GetProcessId() int GetProcessId()
{ {
@ -30,11 +31,11 @@ static BaseConnectionWin Connection;
c = nullptr; c = nullptr;
} }
bool BaseConnection::Open() bool BaseConnection::Open(int pipe)
{ {
wchar_t pipeName[]{L"\\\\?\\pipe\\discord-ipc-0"}; wchar_t pipeName[]{L"\\\\?\\pipe\\discord-ipc-0"};
const size_t pipeDigit = sizeof(pipeName) / sizeof(wchar_t) - 2; const size_t pipeDigit = sizeof(pipeName) / sizeof(wchar_t) - 2;
pipeName[pipeDigit] = L'0'; pipeName[pipeDigit] += pipe;
auto self = reinterpret_cast<BaseConnectionWin*>(this); auto self = reinterpret_cast<BaseConnectionWin*>(this);
for (;;) { for (;;) {
self->pipe = ::CreateFileW( self->pipe = ::CreateFileW(

View File

@ -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

View File

@ -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.
@ -32,9 +33,11 @@ extern "C" void Discord_Register(const char* applicationId, const char* command)
char exePath[1024]; char exePath[1024];
if (!command || !command[0]) { if (!command || !command[0]) {
if (readlink("/proc/self/exe", exePath, sizeof(exePath)) <= 0) { ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath));
if (size <= 0 || size >= (ssize_t)sizeof(exePath)) {
return; return;
} }
exePath[size] = '\0';
command = exePath; command = exePath;
} }
@ -90,7 +93,8 @@ 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);

View File

@ -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,17 @@
*/ */
#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 +45,28 @@ 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 = RegCreateKeyExW(hkey, subkey, 0, 0, 0, KEY_ALL_ACCESS, 0, &hsubkey, 0)) !=
if ((ret = RegCreateKeyW( hkey, subkey, &hsubkey )) != ERROR_SUCCESS) return ret; ERROR_SUCCESS)
hkey = hsubkey; return ret;
} htkey = hsubkey;
ret = RegSetValueExW( hkey, name, 0, type, (const BYTE*)data, len ); }
if (hsubkey) RegCloseKey( hsubkey ); ret = RegSetValueExW(htkey, name, 0, type, (const BYTE*)data, len);
return ret; if (hsubkey && hsubkey != hkey)
RegCloseKey(hsubkey);
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 +80,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 +130,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 +146,8 @@ 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);

View File

@ -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"
@ -32,7 +32,7 @@ struct QueuedMessage {
} }
}; };
struct JoinRequest { struct User {
// snowflake (64bit int), turned into a ascii decimal string, at most 20 chars +1 null // snowflake (64bit int), turned into a ascii decimal string, at most 20 chars +1 null
// terminator = 21 // terminator = 21
char userId[32]; char userId[32];
@ -47,12 +47,14 @@ struct JoinRequest {
}; };
static RpcConnection* Connection{nullptr}; static RpcConnection* Connection{nullptr};
static DiscordEventHandlers QueuedHandlers{};
static DiscordEventHandlers Handlers{}; static DiscordEventHandlers Handlers{};
static std::atomic_bool WasJustConnected{false}; static std::atomic_bool WasJustConnected{false};
static std::atomic_bool WasJustDisconnected{false}; 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};
@ -60,9 +62,11 @@ 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<User, 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
@ -86,10 +90,11 @@ 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();
} }
}); });
} }
@ -115,7 +120,7 @@ public:
void Notify() {} void Notify() {}
}; };
#endif // DISCORD_DISABLE_IO_THREAD #endif // DISCORD_DISABLE_IO_THREAD
static IoThreadHolder IoThread; static IoThreadHolder* IoThread{nullptr};
static void UpdateReconnectTime() static void UpdateReconnectTime()
{ {
@ -210,17 +215,17 @@ static void Discord_UpdateConnection(void)
} }
// writes // writes
if (QueuedPresence.length) { if (UpdatePresence.exchange(false) && 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(); }
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(); UpdatePresence.exchange(true);
} }
} }
@ -234,7 +239,9 @@ static void Discord_UpdateConnection(void)
static void SignalIOActivity() static void SignalIOActivity()
{ {
IoThread.Notify(); if (IoThread != nullptr) {
IoThread->Notify();
}
} }
static bool RegisterForEvent(const char* evtName) static bool RegisterForEvent(const char* evtName)
@ -250,11 +257,30 @@ 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,
const char* optionalSteamId) const char* optionalSteamId,
int pipe)
{ {
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);
@ -266,10 +292,16 @@ 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);
}
else { if (handlers) {
QueuedHandlers = *handlers;
}
else {
QueuedHandlers = {};
}
Handlers = {}; Handlers = {};
} }
@ -277,22 +309,34 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
return; return;
} }
Connection = RpcConnection::Create(applicationId); Connection = RpcConnection::Create(applicationId, pipe);
Connection->onConnect = []() { Connection->onConnect = [](JsonDocument& readyMessage) {
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();
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;
@ -301,7 +345,7 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
UpdateReconnectTime(); UpdateReconnectTime();
}; };
IoThread.Start(); IoThread->Start();
} }
extern "C" DISCORD_EXPORT void Discord_Shutdown(void) extern "C" DISCORD_EXPORT void Discord_Shutdown(void)
@ -312,16 +356,25 @@ extern "C" DISCORD_EXPORT void Discord_Shutdown(void)
Connection->onConnect = nullptr; Connection->onConnect = nullptr;
Connection->onDisconnect = nullptr; Connection->onDisconnect = nullptr;
Handlers = {}; Handlers = {};
IoThread.Stop(); QueuedPresence.length = 0;
UpdatePresence.exchange(false);
if (IoThread != nullptr) {
IoThread->Stop();
delete IoThread;
IoThread = nullptr;
}
RpcConnection::Destroy(Connection); RpcConnection::Destroy(Connection);
} }
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);
UpdatePresence.exchange(true);
}
SignalIOActivity(); SignalIOActivity();
} }
@ -360,25 +413,42 @@ 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) {
DiscordUser du{connectedUser.userId,
connectedUser.username,
connectedUser.discriminator,
connectedUser.avatar};
Handlers.ready(&du);
}
} }
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 +458,48 @@ 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) {
DiscordUser du{req->userId, req->username, req->discriminator, req->avatar};
Handlers.joinRequest(&du);
}
} }
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;
}

View File

@ -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;

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

@ -6,10 +6,11 @@
static const int RpcVersion = 1; static const int RpcVersion = 1;
static RpcConnection Instance; static RpcConnection Instance;
/*static*/ RpcConnection* RpcConnection::Create(const char* applicationId) /*static*/ RpcConnection* RpcConnection::Create(const char* applicationId, int pipe)
{ {
Instance.connection = BaseConnection::Create(); Instance.connection = BaseConnection::Create();
StringCopy(Instance.appId, applicationId); StringCopy(Instance.appId, applicationId);
Instance.pipe = pipe;
return &Instance; return &Instance;
} }
@ -26,12 +27,8 @@ void RpcConnection::Open()
return; return;
} }
if (state == State::Disconnected) { if (state == State::Disconnected && !connection->Open(Instance.pipe)) {
if (connection->Open()) { return;
}
else {
return;
}
} }
if (state == State::SentHandshake) { if (state == State::SentHandshake) {
@ -42,7 +39,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(); onConnect(message);
} }
} }
} }

View File

@ -40,14 +40,15 @@ struct RpcConnection {
BaseConnection* connection{nullptr}; BaseConnection* connection{nullptr};
State state{State::Disconnected}; State state{State::Disconnected};
void (*onConnect)(){nullptr}; void (*onConnect)(JsonDocument& message){nullptr};
void (*onDisconnect)(int errorCode, const char* message){nullptr}; void (*onDisconnect)(int errorCode, const char* message){nullptr};
char appId[64]{}; char appId[64]{};
int pipe;
int lastErrorCode{0}; int lastErrorCode{0};
char lastErrorMessage[256]{}; char lastErrorMessage[256]{};
RpcConnection::MessageFrame sendFrame; RpcConnection::MessageFrame sendFrame;
static RpcConnection* Create(const char* applicationId); static RpcConnection* Create(const char* applicationId, int pipe);
static void Destroy(RpcConnection*&); static void Destroy(RpcConnection*&);
inline bool IsOpen() const { return state == State::Connected; } inline bool IsOpen() const { return state == State::Connected; }

View File

@ -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)
@ -102,8 +102,7 @@ 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);
@ -138,12 +137,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 +196,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);

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"
@ -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