mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #6983 from yourWaifu/add-discord-rpc-support
Add Discord Rich Presence support
This commit is contained in:
@@ -28,6 +28,7 @@ option(ENABLE_HEADLESS "Enables running Dolphin as a headless variant" OFF)
|
||||
option(ENABLE_ALSA "Enables ALSA sound backend" ON)
|
||||
option(ENABLE_PULSEAUDIO "Enables PulseAudio sound backend" ON)
|
||||
option(ENABLE_LLVM "Enables LLVM support, for disassembly" ON)
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence, show the current game on Discord" ON)
|
||||
|
||||
# Maintainers: if you consider blanket disabling this for your users, please
|
||||
# consider the following points:
|
||||
@@ -366,6 +367,7 @@ if(ANDROID)
|
||||
set(USE_EGL 1)
|
||||
set(ENABLE_WX 0)
|
||||
set(ENABLE_QT2 0)
|
||||
set(USE_DISCORD_PRESENCE 0)
|
||||
|
||||
# We are cross compiling, search only the toolchain for libraries and includes
|
||||
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
@@ -388,6 +390,7 @@ if(ENABLE_HEADLESS)
|
||||
set(USE_X11 0)
|
||||
set(ENABLE_WX 0)
|
||||
set(ENABLE_QT2 0)
|
||||
set(USE_DISCORD_PRESENCE 0)
|
||||
add_definitions(-DUSE_HEADLESS)
|
||||
endif()
|
||||
|
||||
@@ -717,6 +720,11 @@ if(ENABLE_WX)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(USE_DISCORD_PRESENCE)
|
||||
message(STATUS "Using static DiscordRPC from Externals")
|
||||
add_subdirectory(Externals/discord-rpc)
|
||||
endif()
|
||||
|
||||
find_package(Libsystemd)
|
||||
if(SYSTEMD_FOUND)
|
||||
message(STATUS "libsystemd found, enabling traversal server watchdog support")
|
||||
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
cmake_minimum_required (VERSION 3.2.0)
|
||||
project (DiscordRPC)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# format
|
||||
file(GLOB_RECURSE ALL_SOURCE_FILES
|
||||
include/*.h
|
||||
src/*.cpp src/*.h src/*.c
|
||||
)
|
||||
|
||||
# Set CLANG_FORMAT_SUFFIX if you are using custom clang-format, e.g. clang-format-5.0
|
||||
find_program(CLANG_FORMAT_CMD clang-format${CLANG_FORMAT_SUFFIX})
|
||||
|
||||
if (CLANG_FORMAT_CMD)
|
||||
add_custom_target(
|
||||
clangformat
|
||||
COMMAND ${CLANG_FORMAT_CMD}
|
||||
-i -style=file -fallback-style=none
|
||||
${ALL_SOURCE_FILES}
|
||||
DEPENDS
|
||||
${ALL_SOURCE_FILES}
|
||||
)
|
||||
endif(CLANG_FORMAT_CMD)
|
||||
|
||||
# add subdirs
|
||||
|
||||
add_subdirectory(src)
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
Copyright 2017 Discord, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Vendored
+91
@@ -0,0 +1,91 @@
|
||||
# Discord RPC
|
||||
|
||||
This is a library for interfacing your game with a locally running Discord desktop client. It's known to work on Windows, macOS, and Linux. You can use the lib directly if you like, or use it as a guide to writing your own if it doesn't suit your game as is. PRs/feedback welcome if you have an improvement everyone might want, or can describe how this doesn't meet your needs.
|
||||
|
||||
Included here are some quick demos that implement the very minimal subset to show current status, and
|
||||
have callbacks for where a more complete game would do more things (joining, spectating, etc).
|
||||
|
||||
## Documentation
|
||||
|
||||
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
|
||||
|
||||
Zeroith, you should be set up to build things because you are a game developer, right?
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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:
|
||||
```sh
|
||||
cd <path to discord-rpc>
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_INSTALL_PREFIX=<path to install discord-rpc to>
|
||||
cmake --build . --config Release --target install
|
||||
```
|
||||
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`.
|
||||
|
||||
There are some CMake options you might care about:
|
||||
|
||||
| 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.
|
||||
| `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
|
||||
| `WARNINGS_AS_ERRORS` | `OFF` | When enabled, compiles with `-Werror` (on *nix platforms). |
|
||||
|
||||
## Continuous Builds
|
||||
|
||||
Why do we have three of these? Three times the fun!
|
||||
|
||||
| CI | badge |
|
||||
|----|-------|
|
||||
| TravisCI | [](https://travis-ci.org/discordapp/discord-rpc)
|
||||
| AppVeyor | [](https://ci.appveyor.com/project/crmarsh/discord-rpc)
|
||||
| Buildkite (internal) | [](https://buildkite.com/discord/discord-rpc)
|
||||
|
||||
## Sample: send-presence
|
||||
|
||||
This is a text adventure "game" that inits/deinits the connection to Discord, and sends a presence update on each command.
|
||||
|
||||
## 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. Run `python build.py unity` in the root directory to build the correct library files and place them in their respective folders.
|
||||
|
||||
## 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. 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
|
||||
|
||||
Below is a table of unofficial, community-developed wrappers for and implementations of Rich Presence in various languages. If you would like to have yours added, please make a pull request adding your repository to the table. The repository should include:
|
||||
|
||||
- The code
|
||||
- A brief ReadMe of how to use it
|
||||
- A working example
|
||||
|
||||
###### Rich Presence Wrappers and Implementations
|
||||
|
||||
| Name | Language |
|
||||
|------|----------|
|
||||
| [DerelictDiscordRPC](https://github.com/voidblaster/DerelictDiscordRPC) | [D](https://dlang.org/) |
|
||||
| [discord-rpc.jar](https://github.com/Vatuu/discord-rpc "Discord-RPC.jar") | Java |
|
||||
| [java-discord-rpc](https://github.com/MinnDevelopment/java-discord-rpc) | Java |
|
||||
| [Discord-IPC](https://github.com/jagrosh/DiscordIPC) | Java |
|
||||
| [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) |
|
||||
Vendored
+304
@@ -0,0 +1,304 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import shutil
|
||||
import zipfile
|
||||
from contextlib import contextmanager
|
||||
import click
|
||||
|
||||
|
||||
def get_platform():
|
||||
""" a name for the platform """
|
||||
if sys.platform.startswith('win'):
|
||||
return 'win'
|
||||
elif sys.platform == 'darwin':
|
||||
return 'osx'
|
||||
elif sys.platform.startswith('linux'):
|
||||
return 'linux'
|
||||
raise Exception('Unsupported platform ' + sys.platform)
|
||||
|
||||
|
||||
SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
# we use Buildkite which sets this env variable by default
|
||||
IS_BUILD_MACHINE = os.environ.get('CI', '') == 'true'
|
||||
PLATFORM = get_platform()
|
||||
INSTALL_ROOT = os.path.join(SCRIPT_PATH, 'builds', 'install')
|
||||
|
||||
|
||||
def get_signtool():
|
||||
""" get path to code signing tool """
|
||||
if PLATFORM == 'win':
|
||||
sdk_dir = 'c:\\Program Files (x86)\\Windows Kits\\10' # os.environ['WindowsSdkDir']
|
||||
return os.path.join(sdk_dir, 'bin', 'x86', 'signtool.exe')
|
||||
elif PLATFORM == 'osx':
|
||||
return '/usr/bin/codesign'
|
||||
|
||||
|
||||
@contextmanager
|
||||
def cd(new_dir):
|
||||
""" Temporarily change current directory """
|
||||
if new_dir:
|
||||
old_dir = os.getcwd()
|
||||
os.chdir(new_dir)
|
||||
yield
|
||||
if new_dir:
|
||||
os.chdir(old_dir)
|
||||
|
||||
|
||||
def mkdir_p(path):
|
||||
""" mkdir -p """
|
||||
if not os.path.isdir(path):
|
||||
click.secho('Making ' + path, fg='yellow')
|
||||
os.makedirs(path)
|
||||
|
||||
|
||||
@click.group(invoke_without_command=True)
|
||||
@click.pass_context
|
||||
@click.option('--clean', is_flag=True)
|
||||
def cli(ctx, clean):
|
||||
""" click wrapper for command line stuff """
|
||||
if ctx.invoked_subcommand is None:
|
||||
ctx.invoke(libs, clean=clean)
|
||||
if IS_BUILD_MACHINE:
|
||||
ctx.invoke(sign)
|
||||
ctx.invoke(archive)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.pass_context
|
||||
def unity(ctx):
|
||||
""" build just dynamic libs for use in unity project """
|
||||
ctx.invoke(libs, clean=False, static=False, shared=True, skip_formatter=True, just_release=True)
|
||||
BUILDS = []
|
||||
|
||||
click.echo('--- Copying libs and header into unity example')
|
||||
UNITY_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'button-clicker', 'Assets', 'Plugins')
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
LIBRARY_NAME = 'discord-rpc.dll'
|
||||
BUILD_64_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release')
|
||||
UNITY_64_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86_64')
|
||||
BUILDS.append({BUILD_64_BASE_PATH: UNITY_64_DLL_PATH})
|
||||
|
||||
BUILD_32_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win32-dynamic', 'src', 'Release')
|
||||
UNITY_32_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86')
|
||||
BUILDS.append({BUILD_32_BASE_PATH: UNITY_32_DLL_PATH})
|
||||
|
||||
elif sys.platform == 'darwin':
|
||||
LIBRARY_NAME = 'discord-rpc.bundle'
|
||||
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'osx-dynamic', 'src')
|
||||
UNITY_DLL_PATH = UNITY_PROJECT_PATH
|
||||
os.rename(
|
||||
os.path.join(BUILD_BASE_PATH, 'libdiscord-rpc.dylib'), os.path.join(BUILD_BASE_PATH, 'discord-rpc.bundle'))
|
||||
|
||||
BUILDS.append({BUILD_BASE_PATH: UNITY_DLL_PATH})
|
||||
|
||||
elif sys.platform.startswith('linux'):
|
||||
LIBRARY_NAME = 'discord-rpc.so'
|
||||
BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'linux-dynamic', 'src')
|
||||
UNITY_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86')
|
||||
os.rename(os.path.join(BUILD_BASE_PATH, 'libdiscord-rpc.so'), os.path.join(BUILD_BASE_PATH, 'discord-rpc.so'))
|
||||
|
||||
BUILDS.append({BUILD_BASE_PATH: UNITY_DLL_PATH})
|
||||
|
||||
else:
|
||||
raise Exception('Unsupported platform ' + sys.platform)
|
||||
|
||||
for build in BUILDS:
|
||||
for i in build:
|
||||
mkdir_p(build[i])
|
||||
shutil.copy(os.path.join(i, LIBRARY_NAME), build[i])
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.pass_context
|
||||
def unreal(ctx):
|
||||
""" build libs and copy them into the unreal project """
|
||||
ctx.invoke(libs, clean=False, static=False, shared=True, skip_formatter=True, just_release=True)
|
||||
BUILDS = []
|
||||
|
||||
click.echo('--- Copying libs and header into unreal example')
|
||||
UNREAL_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc')
|
||||
UNREAL_INCLUDE_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Include')
|
||||
mkdir_p(UNREAL_INCLUDE_PATH)
|
||||
shutil.copy(os.path.join(SCRIPT_PATH, 'include', 'discord_rpc.h'), UNREAL_INCLUDE_PATH)
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
LIBRARY_NAME = 'discord-rpc.lib'
|
||||
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):
|
||||
""" Create a dir under builds, run build and install in it """
|
||||
build_path = os.path.join(SCRIPT_PATH, 'builds', build_name)
|
||||
install_path = os.path.join(INSTALL_ROOT, build_name)
|
||||
mkdir_p(build_path)
|
||||
mkdir_p(install_path)
|
||||
with cd(build_path):
|
||||
initial_cmake = ['cmake', SCRIPT_PATH, '-DCMAKE_INSTALL_PREFIX=%s' % os.path.join('..', 'install', build_name)]
|
||||
if generator:
|
||||
initial_cmake.extend(['-G', generator])
|
||||
for key in options:
|
||||
val = options[key]
|
||||
if type(val) is bool:
|
||||
val = 'ON' if val else 'OFF'
|
||||
initial_cmake.append('-D%s=%s' % (key, val))
|
||||
click.echo('--- Building ' + build_name)
|
||||
subprocess.check_call(initial_cmake)
|
||||
if not just_release:
|
||||
subprocess.check_call(['cmake', '--build', '.', '--config', 'Debug'])
|
||||
subprocess.check_call(['cmake', '--build', '.', '--config', 'Release', '--target', 'install'])
|
||||
|
||||
|
||||
@cli.command()
|
||||
def archive():
|
||||
""" create zip of install dir """
|
||||
click.echo('--- Archiving')
|
||||
archive_file_path = os.path.join(SCRIPT_PATH, 'builds', 'discord-rpc-%s.zip' % get_platform())
|
||||
archive_file = zipfile.ZipFile(archive_file_path, 'w', zipfile.ZIP_DEFLATED)
|
||||
archive_src_base_path = INSTALL_ROOT
|
||||
archive_dst_base_path = 'discord-rpc'
|
||||
with cd(archive_src_base_path):
|
||||
for path, _, filenames in os.walk('.'):
|
||||
for fname in filenames:
|
||||
fpath = os.path.join(path, fname)
|
||||
dst_path = os.path.normpath(os.path.join(archive_dst_base_path, fpath))
|
||||
click.echo('Adding ' + dst_path)
|
||||
archive_file.write(fpath, dst_path)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def sign():
|
||||
""" Do code signing within install directory using our cert """
|
||||
tool = get_signtool()
|
||||
signable_extensions = set()
|
||||
if PLATFORM == 'win':
|
||||
signable_extensions.add('.dll')
|
||||
sign_command_base = [
|
||||
tool,
|
||||
'sign',
|
||||
'/n',
|
||||
'Discord Inc.',
|
||||
'/a',
|
||||
'/tr',
|
||||
'http://timestamp.digicert.com/rfc3161',
|
||||
'/as',
|
||||
'/td',
|
||||
'sha256',
|
||||
'/fd',
|
||||
'sha256',
|
||||
]
|
||||
elif PLATFORM == 'osx':
|
||||
signable_extensions.add('.dylib')
|
||||
sign_command_base = [
|
||||
tool,
|
||||
'--keychain',
|
||||
os.path.expanduser('~/Library/Keychains/login.keychain'),
|
||||
'-vvvv',
|
||||
'--deep',
|
||||
'--force',
|
||||
'--sign',
|
||||
'Developer ID Application: Hammer & Chisel Inc. (53Q6R32WPB)',
|
||||
]
|
||||
else:
|
||||
click.secho('Not signing things on this platform yet', fg='red')
|
||||
return
|
||||
|
||||
click.echo('--- Signing')
|
||||
for path, _, filenames in os.walk(INSTALL_ROOT):
|
||||
for fname in filenames:
|
||||
ext = os.path.splitext(fname)[1]
|
||||
if ext not in signable_extensions:
|
||||
continue
|
||||
fpath = os.path.join(path, fname)
|
||||
click.echo('Sign ' + fpath)
|
||||
sign_command = sign_command_base + [fpath]
|
||||
subprocess.check_call(sign_command)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--clean', is_flag=True)
|
||||
@click.option('--static', is_flag=True)
|
||||
@click.option('--shared', is_flag=True)
|
||||
@click.option('--skip_formatter', is_flag=True)
|
||||
@click.option('--just_release', is_flag=True)
|
||||
def libs(clean, static, shared, skip_formatter, just_release):
|
||||
""" Do all the builds for this platform """
|
||||
if clean:
|
||||
shutil.rmtree('builds', ignore_errors=True)
|
||||
|
||||
mkdir_p('builds')
|
||||
|
||||
if not (static or shared):
|
||||
static = True
|
||||
shared = True
|
||||
|
||||
static_options = {}
|
||||
dynamic_options = {
|
||||
'BUILD_SHARED_LIBS': True,
|
||||
'USE_STATIC_CRT': True,
|
||||
}
|
||||
|
||||
if skip_formatter or IS_BUILD_MACHINE:
|
||||
static_options['CLANG_FORMAT_SUFFIX'] = 'none'
|
||||
dynamic_options['CLANG_FORMAT_SUFFIX'] = 'none'
|
||||
|
||||
if IS_BUILD_MACHINE:
|
||||
just_release = True
|
||||
static_options['WARNINGS_AS_ERRORS'] = True
|
||||
dynamic_options['WARNINGS_AS_ERRORS'] = True
|
||||
|
||||
if PLATFORM == 'win':
|
||||
generator32 = 'Visual Studio 14 2015'
|
||||
generator64 = 'Visual Studio 14 2015 Win64'
|
||||
if static:
|
||||
build_lib('win32-static', generator32, static_options, just_release)
|
||||
build_lib('win64-static', generator64, static_options, just_release)
|
||||
if shared:
|
||||
build_lib('win32-dynamic', generator32, dynamic_options, just_release)
|
||||
build_lib('win64-dynamic', generator64, dynamic_options, just_release)
|
||||
elif PLATFORM == 'osx':
|
||||
if static:
|
||||
build_lib('osx-static', None, static_options, just_release)
|
||||
if shared:
|
||||
build_lib('osx-dynamic', None, dynamic_options, just_release)
|
||||
elif PLATFORM == 'linux':
|
||||
if static:
|
||||
build_lib('linux-static', None, static_options, just_release)
|
||||
if shared:
|
||||
build_lib('linux-dynamic', None, dynamic_options, just_release)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.chdir(SCRIPT_PATH)
|
||||
sys.exit(cli())
|
||||
@@ -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
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// clang-format off
|
||||
|
||||
#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
|
||||
|
||||
// clang-format on
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct DiscordRichPresence {
|
||||
const char* state; /* max 128 bytes */
|
||||
const char* details; /* max 128 bytes */
|
||||
int64_t startTimestamp;
|
||||
int64_t endTimestamp;
|
||||
const char* largeImageKey; /* max 32 bytes */
|
||||
const char* largeImageText; /* max 128 bytes */
|
||||
const char* smallImageKey; /* max 32 bytes */
|
||||
const char* smallImageText; /* max 128 bytes */
|
||||
const char* partyId; /* max 128 bytes */
|
||||
int partySize;
|
||||
int partyMax;
|
||||
const char* matchSecret; /* max 128 bytes */
|
||||
const char* joinSecret; /* max 128 bytes */
|
||||
const char* spectateSecret; /* max 128 bytes */
|
||||
int8_t instance;
|
||||
} DiscordRichPresence;
|
||||
|
||||
typedef struct DiscordUser {
|
||||
const char* userId;
|
||||
const char* username;
|
||||
const char* discriminator;
|
||||
const char* avatar;
|
||||
} DiscordUser;
|
||||
|
||||
typedef struct DiscordEventHandlers {
|
||||
void (*ready)(const DiscordUser* request);
|
||||
void (*disconnected)(int errorCode, const char* message);
|
||||
void (*errored)(int errorCode, const char* message);
|
||||
void (*joinGame)(const char* joinSecret);
|
||||
void (*spectateGame)(const char* spectateSecret);
|
||||
void (*joinRequest)(const DiscordUser* request);
|
||||
} DiscordEventHandlers;
|
||||
|
||||
#define DISCORD_REPLY_NO 0
|
||||
#define DISCORD_REPLY_YES 1
|
||||
#define DISCORD_REPLY_IGNORE 2
|
||||
|
||||
DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
|
||||
DiscordEventHandlers* handlers,
|
||||
int autoRegister,
|
||||
const char* optionalSteamId);
|
||||
DISCORD_EXPORT void Discord_Shutdown(void);
|
||||
|
||||
/* checks for incoming messages, dispatches callbacks */
|
||||
DISCORD_EXPORT void Discord_RunCallbacks(void);
|
||||
|
||||
/* If you disable the lib starting its own io thread, you'll need to call this from your own */
|
||||
#ifdef DISCORD_DISABLE_IO_THREAD
|
||||
DISCORD_EXPORT void Discord_UpdateConnection(void);
|
||||
#endif
|
||||
|
||||
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence);
|
||||
DISCORD_EXPORT void Discord_ClearPresence(void);
|
||||
|
||||
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
|
||||
|
||||
DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/thirdparty/include)
|
||||
|
||||
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(WARNINGS_AS_ERRORS "When enabled, compiles with `-Werror` (on *nix platforms)." OFF)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
|
||||
set(BASE_RPC_SRC
|
||||
${PROJECT_SOURCE_DIR}/include/discord_rpc.h
|
||||
discord_rpc.cpp
|
||||
${PROJECT_SOURCE_DIR}/include/discord_register.h
|
||||
rpc_connection.h
|
||||
rpc_connection.cpp
|
||||
serialization.h
|
||||
serialization.cpp
|
||||
connection.h
|
||||
backoff.h
|
||||
msg_queue.h
|
||||
)
|
||||
|
||||
if (${BUILD_SHARED_LIBS})
|
||||
if(WIN32)
|
||||
set(BASE_RPC_SRC ${BASE_RPC_SRC} dllmain.cpp)
|
||||
endif(WIN32)
|
||||
endif(${BUILD_SHARED_LIBS})
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-DDISCORD_WINDOWS)
|
||||
set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_win.cpp discord_register_win.cpp)
|
||||
add_library(discord-rpc ${BASE_RPC_SRC})
|
||||
if (MSVC)
|
||||
if(USE_STATIC_CRT)
|
||||
foreach(CompilerFlag
|
||||
CMAKE_CXX_FLAGS
|
||||
CMAKE_CXX_FLAGS_DEBUG
|
||||
CMAKE_CXX_FLAGS_RELEASE
|
||||
CMAKE_C_FLAGS
|
||||
CMAKE_C_FLAGS_DEBUG
|
||||
CMAKE_C_FLAGS_RELEASE)
|
||||
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
|
||||
endforeach()
|
||||
endif(USE_STATIC_CRT)
|
||||
target_compile_options(discord-rpc PRIVATE /EHsc
|
||||
/Wall
|
||||
/wd4100 # unreferenced formal parameter
|
||||
/wd4514 # unreferenced inline
|
||||
/wd4625 # copy constructor deleted
|
||||
/wd5026 # move constructor deleted
|
||||
/wd4626 # move assignment operator deleted
|
||||
/wd4668 # not defined preprocessor macro
|
||||
/wd4710 # function not inlined
|
||||
/wd4711 # function was inlined
|
||||
/wd4820 # structure padding
|
||||
/wd4946 # reinterpret_cast used between related classes
|
||||
/wd5027 # move assignment operator was implicitly defined as deleted
|
||||
)
|
||||
endif(MSVC)
|
||||
target_link_libraries(discord-rpc PRIVATE psapi advapi32)
|
||||
endif(WIN32)
|
||||
|
||||
if(UNIX)
|
||||
set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_unix.cpp)
|
||||
|
||||
if (APPLE)
|
||||
add_definitions(-DDISCORD_OSX)
|
||||
set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_osx.m)
|
||||
else (APPLE)
|
||||
add_definitions(-DDISCORD_LINUX)
|
||||
set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_linux.cpp)
|
||||
endif(APPLE)
|
||||
|
||||
add_library(discord-rpc ${BASE_RPC_SRC})
|
||||
target_link_libraries(discord-rpc PUBLIC pthread)
|
||||
target_compile_options(discord-rpc PRIVATE
|
||||
-g
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wpedantic
|
||||
)
|
||||
|
||||
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-old-style-cast # it's fine
|
||||
-Wno-c++98-compat # that was almost 2 decades ago
|
||||
-Wno-c++98-compat-pedantic
|
||||
-Wno-missing-noreturn
|
||||
-Wno-padded # structure padding
|
||||
-Wno-covered-switch-default
|
||||
-Wno-exit-time-destructors # not sure about these
|
||||
-Wno-global-constructors
|
||||
)
|
||||
|
||||
if (${BUILD_SHARED_LIBS})
|
||||
target_compile_options(discord-rpc PRIVATE -fPIC)
|
||||
endif (${BUILD_SHARED_LIBS})
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(discord-rpc PRIVATE "-framework AppKit")
|
||||
endif (APPLE)
|
||||
endif(UNIX)
|
||||
|
||||
target_include_directories(discord-rpc PRIVATE ${RAPIDJSON}/include)
|
||||
|
||||
if (NOT ${ENABLE_IO_THREAD})
|
||||
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_DISABLE_IO_THREAD)
|
||||
endif (NOT ${ENABLE_IO_THREAD})
|
||||
|
||||
if (${BUILD_SHARED_LIBS})
|
||||
target_compile_definitions(discord-rpc PUBLIC -DDISCORD_DYNAMIC_LIB)
|
||||
target_compile_definitions(discord-rpc PRIVATE -DDISCORD_BUILDING_SDK)
|
||||
endif(${BUILD_SHARED_LIBS})
|
||||
|
||||
if (CLANG_FORMAT_CMD)
|
||||
add_dependencies(discord-rpc clangformat)
|
||||
endif(CLANG_FORMAT_CMD)
|
||||
|
||||
# install
|
||||
|
||||
install(
|
||||
TARGETS discord-rpc
|
||||
EXPORT "discord-rpc"
|
||||
RUNTIME
|
||||
DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||
LIBRARY
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
ARCHIVE
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
INCLUDES
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
)
|
||||
|
||||
install(
|
||||
FILES
|
||||
"../include/discord_rpc.h"
|
||||
"../include/discord_register.h"
|
||||
DESTINATION "include"
|
||||
)
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
struct Backoff {
|
||||
int64_t minAmount;
|
||||
int64_t maxAmount;
|
||||
int64_t current;
|
||||
int fails;
|
||||
std::mt19937_64 randGenerator;
|
||||
std::uniform_real_distribution<> randDistribution;
|
||||
|
||||
double rand01() { return randDistribution(randGenerator); }
|
||||
|
||||
Backoff(int64_t min, int64_t max)
|
||||
: minAmount(min)
|
||||
, maxAmount(max)
|
||||
, current(min)
|
||||
, fails(0)
|
||||
, randGenerator((uint64_t)time(0))
|
||||
{
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
fails = 0;
|
||||
current = minAmount;
|
||||
}
|
||||
|
||||
int64_t nextDelay()
|
||||
{
|
||||
++fails;
|
||||
int64_t delay = (int64_t)((double)current * 2.0 * rand01());
|
||||
current = std::min(current + delay, maxAmount);
|
||||
return current;
|
||||
}
|
||||
};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
// This is to wrap the platform specific kinds of connect/read/write.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// not really connectiony, but need per-platform
|
||||
int GetProcessId();
|
||||
|
||||
struct BaseConnection {
|
||||
static BaseConnection* Create();
|
||||
static void Destroy(BaseConnection*&);
|
||||
bool isOpen{false};
|
||||
bool Open();
|
||||
bool Close();
|
||||
bool Write(const void* data, size_t length);
|
||||
bool Read(void* data, size_t length);
|
||||
};
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
#include "connection.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int GetProcessId()
|
||||
{
|
||||
return ::getpid();
|
||||
}
|
||||
|
||||
struct BaseConnectionUnix : public BaseConnection {
|
||||
int sock{-1};
|
||||
};
|
||||
|
||||
static BaseConnectionUnix Connection;
|
||||
static sockaddr_un PipeAddr{};
|
||||
#ifdef MSG_NOSIGNAL
|
||||
static int MsgFlags = MSG_NOSIGNAL;
|
||||
#else
|
||||
static int MsgFlags = 0;
|
||||
#endif
|
||||
|
||||
static const char* GetTempPath()
|
||||
{
|
||||
const char* temp = getenv("XDG_RUNTIME_DIR");
|
||||
temp = temp ? temp : getenv("TMPDIR");
|
||||
temp = temp ? temp : getenv("TMP");
|
||||
temp = temp ? temp : getenv("TEMP");
|
||||
temp = temp ? temp : "/tmp";
|
||||
return temp;
|
||||
}
|
||||
|
||||
/*static*/ BaseConnection* BaseConnection::Create()
|
||||
{
|
||||
PipeAddr.sun_family = AF_UNIX;
|
||||
return &Connection;
|
||||
}
|
||||
|
||||
/*static*/ void BaseConnection::Destroy(BaseConnection*& c)
|
||||
{
|
||||
auto self = reinterpret_cast<BaseConnectionUnix*>(c);
|
||||
self->Close();
|
||||
c = nullptr;
|
||||
}
|
||||
|
||||
bool BaseConnection::Open()
|
||||
{
|
||||
const char* tempPath = GetTempPath();
|
||||
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
|
||||
self->sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (self->sock == -1) {
|
||||
return false;
|
||||
}
|
||||
fcntl(self->sock, F_SETFL, O_NONBLOCK);
|
||||
#ifdef SO_NOSIGPIPE
|
||||
int optval = 1;
|
||||
setsockopt(self->sock, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval));
|
||||
#endif
|
||||
|
||||
for (int pipeNum = 0; pipeNum < 10; ++pipeNum) {
|
||||
snprintf(
|
||||
PipeAddr.sun_path, sizeof(PipeAddr.sun_path), "%s/discord-ipc-%d", tempPath, pipeNum);
|
||||
int err = connect(self->sock, (const sockaddr*)&PipeAddr, sizeof(PipeAddr));
|
||||
if (err == 0) {
|
||||
self->isOpen = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
self->Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BaseConnection::Close()
|
||||
{
|
||||
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
|
||||
if (self->sock == -1) {
|
||||
return false;
|
||||
}
|
||||
close(self->sock);
|
||||
self->sock = -1;
|
||||
self->isOpen = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseConnection::Write(const void* data, size_t length)
|
||||
{
|
||||
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
|
||||
|
||||
if (self->sock == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t sentBytes = send(self->sock, data, length, MsgFlags);
|
||||
if (sentBytes < 0) {
|
||||
Close();
|
||||
}
|
||||
return sentBytes == (ssize_t)length;
|
||||
}
|
||||
|
||||
bool BaseConnection::Read(void* data, size_t length)
|
||||
{
|
||||
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
|
||||
|
||||
if (self->sock == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int res = (int)recv(self->sock, data, length, MsgFlags);
|
||||
if (res < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
return false;
|
||||
}
|
||||
Close();
|
||||
}
|
||||
return res == (int)length;
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
#include "connection.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMCX
|
||||
#define NOSERVICE
|
||||
#define NOIME
|
||||
#include <assert.h>
|
||||
#include <windows.h>
|
||||
|
||||
int GetProcessId()
|
||||
{
|
||||
return (int)::GetCurrentProcessId();
|
||||
}
|
||||
|
||||
struct BaseConnectionWin : public BaseConnection {
|
||||
HANDLE pipe{INVALID_HANDLE_VALUE};
|
||||
};
|
||||
|
||||
static BaseConnectionWin Connection;
|
||||
|
||||
/*static*/ BaseConnection* BaseConnection::Create()
|
||||
{
|
||||
return &Connection;
|
||||
}
|
||||
|
||||
/*static*/ void BaseConnection::Destroy(BaseConnection*& c)
|
||||
{
|
||||
auto self = reinterpret_cast<BaseConnectionWin*>(c);
|
||||
self->Close();
|
||||
c = nullptr;
|
||||
}
|
||||
|
||||
bool BaseConnection::Open()
|
||||
{
|
||||
wchar_t pipeName[]{L"\\\\?\\pipe\\discord-ipc-0"};
|
||||
const size_t pipeDigit = sizeof(pipeName) / sizeof(wchar_t) - 2;
|
||||
pipeName[pipeDigit] = L'0';
|
||||
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
||||
for (;;) {
|
||||
self->pipe = ::CreateFileW(
|
||||
pipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (self->pipe != INVALID_HANDLE_VALUE) {
|
||||
self->isOpen = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto lastError = GetLastError();
|
||||
if (lastError == ERROR_FILE_NOT_FOUND) {
|
||||
if (pipeName[pipeDigit] < L'9') {
|
||||
pipeName[pipeDigit]++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (lastError == ERROR_PIPE_BUSY) {
|
||||
if (!WaitNamedPipeW(pipeName, 10000)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool BaseConnection::Close()
|
||||
{
|
||||
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
||||
::CloseHandle(self->pipe);
|
||||
self->pipe = INVALID_HANDLE_VALUE;
|
||||
self->isOpen = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseConnection::Write(const void* data, size_t length)
|
||||
{
|
||||
if (length == 0) {
|
||||
return true;
|
||||
}
|
||||
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
||||
assert(self);
|
||||
if (!self) {
|
||||
return false;
|
||||
}
|
||||
if (self->pipe == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
assert(data);
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
const DWORD bytesLength = (DWORD)length;
|
||||
DWORD bytesWritten = 0;
|
||||
return ::WriteFile(self->pipe, data, bytesLength, &bytesWritten, nullptr) == TRUE &&
|
||||
bytesWritten == bytesLength;
|
||||
}
|
||||
|
||||
bool BaseConnection::Read(void* data, size_t length)
|
||||
{
|
||||
assert(data);
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
auto self = reinterpret_cast<BaseConnectionWin*>(this);
|
||||
assert(self);
|
||||
if (!self) {
|
||||
return false;
|
||||
}
|
||||
if (self->pipe == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
DWORD bytesAvailable = 0;
|
||||
if (::PeekNamedPipe(self->pipe, nullptr, 0, nullptr, &bytesAvailable, nullptr)) {
|
||||
if (bytesAvailable >= length) {
|
||||
DWORD bytesToRead = (DWORD)length;
|
||||
DWORD bytesRead = 0;
|
||||
if (::ReadFile(self->pipe, data, bytesToRead, &bytesRead, nullptr) == TRUE) {
|
||||
assert(bytesToRead == bytesRead);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGUID>{4482FD2A-EC43-3FFB-AC20-2E5C54B05EAD}</ProjectGUID>
|
||||
<WindowsTargetPlatformVersion>10.0.15063.0</WindowsTargetPlatformVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<Platform>x64</Platform>
|
||||
<ProjectName>discord-rpc</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\..\Source\VSProps\Base.props" />
|
||||
<Import Project="..\..\..\Source\VSProps\ClDisableAllWarnings.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<_ProjectFileVersion>10.0.20506.1</_ProjectFileVersion>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..\include;..\thirdparty\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;DISCORD_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>..\include;..\thirdparty\rapidjson-1.1.0\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Midl>
|
||||
<AdditionalIncludeDirectories>..\include;..\thirdparty\rapidjson-1.1.0\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<OutputDirectory>$(ProjectDir)/$(IntDir)</OutputDirectory>
|
||||
<HeaderFileName>%(Filename).h</HeaderFileName>
|
||||
<TypeLibraryName>%(Filename).tlb</TypeLibraryName>
|
||||
<InterfaceIdentifierFileName>%(Filename)_i.c</InterfaceIdentifierFileName>
|
||||
<ProxyFileName>%(Filename)_p.c</ProxyFileName>
|
||||
</Midl>
|
||||
<Lib />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..\include;..\thirdparty\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<DebugInformationFormat>
|
||||
</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;DISCORD_WINDOWS;CMAKE_INTDIR=\"Release\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>..\include;..\thirdparty\rapidjson-1.1.0\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Midl>
|
||||
<AdditionalIncludeDirectories>..\include;..\thirdparty\rapidjson-1.1.0\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<OutputDirectory>$(ProjectDir)/$(IntDir)</OutputDirectory>
|
||||
<HeaderFileName>%(Filename).h</HeaderFileName>
|
||||
<TypeLibraryName>%(Filename).tlb</TypeLibraryName>
|
||||
<InterfaceIdentifierFileName>%(Filename)_i.c</InterfaceIdentifierFileName>
|
||||
<ProxyFileName>%(Filename)_p.c</ProxyFileName>
|
||||
</Midl>
|
||||
<Lib />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\include\discord_rpc.h" />
|
||||
<ClInclude Include="..\discord_register.h" />
|
||||
<ClInclude Include="rpc_connection.h" />
|
||||
<ClInclude Include="serialization.h" />
|
||||
<ClInclude Include="connection.h" />
|
||||
<ClInclude Include="backoff.h" />
|
||||
<ClInclude Include="msg_queue.h" />
|
||||
<ClCompile Include="discord_rpc.cpp" />
|
||||
<ClCompile Include="rpc_connection.cpp" />
|
||||
<ClCompile Include="serialization.cpp" />
|
||||
<ClCompile Include="connection_win.cpp" />
|
||||
<ClCompile Include="discord_register_win.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,100 @@
|
||||
#include "discord_rpc.h"
|
||||
#include "discord_register.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static bool Mkdir(const char* path)
|
||||
{
|
||||
int result = mkdir(path, 0755);
|
||||
if (result == 0) {
|
||||
return true;
|
||||
}
|
||||
if (errno == EEXIST) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// we want to register games so we can run them from Discord client as discord-<appid>://
|
||||
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.
|
||||
|
||||
const char* home = getenv("HOME");
|
||||
if (!home) {
|
||||
return;
|
||||
}
|
||||
|
||||
char exePath[1024];
|
||||
if (!command || !command[0]) {
|
||||
if (readlink("/proc/self/exe", exePath, sizeof(exePath)) <= 0) {
|
||||
return;
|
||||
}
|
||||
command = exePath;
|
||||
}
|
||||
|
||||
const char* destopFileFormat = "[Desktop Entry]\n"
|
||||
"Name=Game %s\n"
|
||||
"Exec=%s %%u\n" // note: it really wants that %u in there
|
||||
"Type=Application\n"
|
||||
"NoDisplay=true\n"
|
||||
"Categories=Discord;Games;\n"
|
||||
"MimeType=x-scheme-handler/discord-%s;\n";
|
||||
char desktopFile[2048];
|
||||
int fileLen = snprintf(
|
||||
desktopFile, sizeof(desktopFile), destopFileFormat, applicationId, command, applicationId);
|
||||
if (fileLen <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char desktopFilename[256];
|
||||
snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId);
|
||||
|
||||
char desktopFilePath[1024];
|
||||
snprintf(desktopFilePath, sizeof(desktopFilePath), "%s/.local", home);
|
||||
if (!Mkdir(desktopFilePath)) {
|
||||
return;
|
||||
}
|
||||
strcat(desktopFilePath, "/share");
|
||||
if (!Mkdir(desktopFilePath)) {
|
||||
return;
|
||||
}
|
||||
strcat(desktopFilePath, "/applications");
|
||||
if (!Mkdir(desktopFilePath)) {
|
||||
return;
|
||||
}
|
||||
strcat(desktopFilePath, desktopFilename);
|
||||
|
||||
FILE* fp = fopen(desktopFilePath, "w");
|
||||
if (fp) {
|
||||
fwrite(desktopFile, 1, fileLen, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
char xdgMimeCommand[1024];
|
||||
snprintf(xdgMimeCommand,
|
||||
sizeof(xdgMimeCommand),
|
||||
"xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s",
|
||||
applicationId,
|
||||
applicationId);
|
||||
if (system(xdgMimeCommand) < 0) {
|
||||
fprintf(stderr, "Failed to register mime handler\n");
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId,
|
||||
const char* steamId)
|
||||
{
|
||||
char command[256];
|
||||
sprintf(command, "xdg-open steam://rungameid/%s", steamId);
|
||||
Discord_Register(applicationId, command);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
#include "discord_register.h"
|
||||
|
||||
static void RegisterCommand(const char* applicationId, const char* command)
|
||||
{
|
||||
// There does not appear to be a way to register arbitrary commands on OSX, so instead we'll save the command
|
||||
// to a file in the Discord config path, and when it is needed, Discord can try to load the file there, open
|
||||
// the command therein (will pass to js's window.open, so requires a url-like thing)
|
||||
|
||||
// Note: will not work for sandboxed apps
|
||||
NSString *home = NSHomeDirectory();
|
||||
if (!home) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *path = [[[[[[home stringByAppendingPathComponent:@"Library"]
|
||||
stringByAppendingPathComponent:@"Application Support"]
|
||||
stringByAppendingPathComponent:@"discord"]
|
||||
stringByAppendingPathComponent:@"games"]
|
||||
stringByAppendingPathComponent:[NSString stringWithUTF8String:applicationId]]
|
||||
stringByAppendingPathExtension:@"json"];
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
|
||||
NSString *jsonBuffer = [NSString stringWithFormat:@"{\"command\": \"%s\"}", command];
|
||||
[jsonBuffer writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil];
|
||||
}
|
||||
|
||||
static void RegisterURL(const char* applicationId)
|
||||
{
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url), "discord-%s", applicationId);
|
||||
CFStringRef cfURL = CFStringCreateWithCString(NULL, url, kCFStringEncodingUTF8);
|
||||
|
||||
NSString* myBundleId = [[NSBundle mainBundle] bundleIdentifier];
|
||||
if (!myBundleId) {
|
||||
fprintf(stderr, "No bundle id found\n");
|
||||
return;
|
||||
}
|
||||
|
||||
NSURL* myURL = [[NSBundle mainBundle] bundleURL];
|
||||
if (!myURL) {
|
||||
fprintf(stderr, "No bundle url found\n");
|
||||
return;
|
||||
}
|
||||
|
||||
OSStatus status = LSSetDefaultHandlerForURLScheme(cfURL, (__bridge CFStringRef)myBundleId);
|
||||
if (status != noErr) {
|
||||
fprintf(stderr, "Error in LSSetDefaultHandlerForURLScheme: %d\n", (int)status);
|
||||
return;
|
||||
}
|
||||
|
||||
status = LSRegisterURL((__bridge CFURLRef)myURL, true);
|
||||
if (status != noErr) {
|
||||
fprintf(stderr, "Error in LSRegisterURL: %d\n", (int)status);
|
||||
}
|
||||
}
|
||||
|
||||
void Discord_Register(const char* applicationId, const char* command)
|
||||
{
|
||||
if (command) {
|
||||
RegisterCommand(applicationId, command);
|
||||
}
|
||||
else {
|
||||
// raii lite
|
||||
@autoreleasepool {
|
||||
RegisterURL(applicationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
|
||||
{
|
||||
char command[256];
|
||||
snprintf(command, 256, "steam://rungameid/%s", steamId);
|
||||
Discord_Register(applicationId, command);
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
#include "discord_rpc.h"
|
||||
#include "discord_register.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMCX
|
||||
#define NOSERVICE
|
||||
#define NOIME
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#include <cwchar>
|
||||
#include <cstdio>
|
||||
|
||||
/**
|
||||
* Updated fixes for MinGW and WinXP
|
||||
* This block is written the way it does not involve changing the rest of the code
|
||||
* Checked to be compiling
|
||||
* 1) strsafe.h belongs to Windows SDK and cannot be added to MinGW
|
||||
* #include guarded, functions redirected to <string.h> substitutes
|
||||
* 2) RegSetKeyValueW and LSTATUS are not declared in <winreg.h>
|
||||
* The entire function is rewritten
|
||||
*/
|
||||
#ifdef __MINGW32__
|
||||
/// strsafe.h fixes
|
||||
static HRESULT StringCbPrintfW(LPWSTR pszDest, size_t cbDest, LPCWSTR pszFormat, ...)
|
||||
{
|
||||
HRESULT ret;
|
||||
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
|
||||
#include <strsafe.h>
|
||||
#endif // __MINGW32__
|
||||
|
||||
/// winreg.h fixes
|
||||
#ifndef LSTATUS
|
||||
#define LSTATUS LONG
|
||||
#endif
|
||||
#ifdef RegSetKeyValueW
|
||||
#undefine RegSetKeyValueW
|
||||
#endif
|
||||
#define RegSetKeyValueW regset
|
||||
static LSTATUS regset(HKEY hkey,
|
||||
LPCWSTR subkey,
|
||||
LPCWSTR name,
|
||||
DWORD type,
|
||||
const void* data,
|
||||
DWORD len)
|
||||
{
|
||||
HKEY htkey = hkey, hsubkey = nullptr;
|
||||
LSTATUS ret;
|
||||
if (subkey && subkey[0]) {
|
||||
if ((ret = RegCreateKeyExW(hkey, subkey, 0, 0, 0, KEY_ALL_ACCESS, 0, &hsubkey, 0)) !=
|
||||
ERROR_SUCCESS)
|
||||
return ret;
|
||||
htkey = hsubkey;
|
||||
}
|
||||
ret = RegSetValueExW(htkey, name, 0, type, (const BYTE*)data, len);
|
||||
if (hsubkey && hsubkey != hkey)
|
||||
RegCloseKey(hsubkey);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command)
|
||||
{
|
||||
// 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>://
|
||||
// Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions.
|
||||
|
||||
wchar_t exeFilePath[MAX_PATH];
|
||||
DWORD exeLen = GetModuleFileNameW(nullptr, exeFilePath, MAX_PATH);
|
||||
wchar_t openCommand[1024];
|
||||
|
||||
if (command && command[0]) {
|
||||
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command);
|
||||
}
|
||||
else {
|
||||
// StringCbCopyW(openCommand, sizeof(openCommand), exeFilePath);
|
||||
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", exeFilePath);
|
||||
}
|
||||
|
||||
wchar_t protocolName[64];
|
||||
StringCbPrintfW(protocolName, sizeof(protocolName), L"discord-%s", applicationId);
|
||||
wchar_t protocolDescription[128];
|
||||
StringCbPrintfW(
|
||||
protocolDescription, sizeof(protocolDescription), L"URL:Run game %s protocol", applicationId);
|
||||
wchar_t urlProtocol = 0;
|
||||
|
||||
wchar_t keyName[256];
|
||||
StringCbPrintfW(keyName, sizeof(keyName), L"Software\\Classes\\%s", protocolName);
|
||||
HKEY key;
|
||||
auto status =
|
||||
RegCreateKeyExW(HKEY_CURRENT_USER, keyName, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr);
|
||||
if (status != ERROR_SUCCESS) {
|
||||
fprintf(stderr, "Error creating key\n");
|
||||
return;
|
||||
}
|
||||
DWORD len;
|
||||
LSTATUS result;
|
||||
len = (DWORD)lstrlenW(protocolDescription) + 1;
|
||||
result =
|
||||
RegSetKeyValueW(key, nullptr, nullptr, REG_SZ, protocolDescription, len * sizeof(wchar_t));
|
||||
if (FAILED(result)) {
|
||||
fprintf(stderr, "Error writing description\n");
|
||||
}
|
||||
|
||||
len = (DWORD)lstrlenW(protocolDescription) + 1;
|
||||
result = RegSetKeyValueW(key, nullptr, L"URL Protocol", REG_SZ, &urlProtocol, sizeof(wchar_t));
|
||||
if (FAILED(result)) {
|
||||
fprintf(stderr, "Error writing description\n");
|
||||
}
|
||||
|
||||
result = RegSetKeyValueW(
|
||||
key, L"DefaultIcon", nullptr, REG_SZ, exeFilePath, (exeLen + 1) * sizeof(wchar_t));
|
||||
if (FAILED(result)) {
|
||||
fprintf(stderr, "Error writing icon\n");
|
||||
}
|
||||
|
||||
len = (DWORD)lstrlenW(openCommand) + 1;
|
||||
result = RegSetKeyValueW(
|
||||
key, L"shell\\open\\command", nullptr, REG_SZ, openCommand, len * sizeof(wchar_t));
|
||||
if (FAILED(result)) {
|
||||
fprintf(stderr, "Error writing command\n");
|
||||
}
|
||||
RegCloseKey(key);
|
||||
}
|
||||
|
||||
extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command)
|
||||
{
|
||||
wchar_t appId[32];
|
||||
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
|
||||
|
||||
wchar_t openCommand[1024];
|
||||
const wchar_t* wcommand = nullptr;
|
||||
if (command && command[0]) {
|
||||
const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand);
|
||||
MultiByteToWideChar(CP_UTF8, 0, command, -1, openCommand, commandBufferLen);
|
||||
wcommand = openCommand;
|
||||
}
|
||||
|
||||
Discord_RegisterW(appId, wcommand);
|
||||
}
|
||||
|
||||
extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId,
|
||||
const char* steamId)
|
||||
{
|
||||
wchar_t appId[32];
|
||||
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
|
||||
|
||||
wchar_t wSteamId[32];
|
||||
MultiByteToWideChar(CP_UTF8, 0, steamId, -1, wSteamId, 32);
|
||||
|
||||
HKEY key;
|
||||
auto status = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam", 0, KEY_READ, &key);
|
||||
if (status != ERROR_SUCCESS) {
|
||||
fprintf(stderr, "Error opening Steam key\n");
|
||||
return;
|
||||
}
|
||||
|
||||
wchar_t steamPath[MAX_PATH];
|
||||
DWORD pathBytes = sizeof(steamPath);
|
||||
status = RegQueryValueExW(key, L"SteamExe", nullptr, nullptr, (BYTE*)steamPath, &pathBytes);
|
||||
RegCloseKey(key);
|
||||
if (status != ERROR_SUCCESS || pathBytes < 1) {
|
||||
fprintf(stderr, "Error reading SteamExe key\n");
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD pathChars = pathBytes / sizeof(wchar_t);
|
||||
for (DWORD i = 0; i < pathChars; ++i) {
|
||||
if (steamPath[i] == L'/') {
|
||||
steamPath[i] = L'\\';
|
||||
}
|
||||
}
|
||||
|
||||
wchar_t command[1024];
|
||||
StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://rungameid/%s", steamPath, wSteamId);
|
||||
|
||||
Discord_RegisterW(appId, command);
|
||||
}
|
||||
+500
File diff suppressed because it is too large
Load Diff
+8
@@ -0,0 +1,8 @@
|
||||
#include <windows.h>
|
||||
|
||||
// outsmart GCC's missing-declarations warning
|
||||
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID);
|
||||
BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
// A simple queue. No locks, but only works with a single thread as producer and a single thread as
|
||||
// a consumer. Mutex up as needed.
|
||||
|
||||
template <typename ElementType, size_t QueueSize>
|
||||
class MsgQueue {
|
||||
ElementType queue_[QueueSize];
|
||||
std::atomic_uint nextAdd_{0};
|
||||
std::atomic_uint nextSend_{0};
|
||||
std::atomic_uint pendingSends_{0};
|
||||
|
||||
public:
|
||||
MsgQueue() {}
|
||||
|
||||
ElementType* GetNextAddMessage()
|
||||
{
|
||||
// if we are falling behind, bail
|
||||
if (pendingSends_.load() >= QueueSize) {
|
||||
return nullptr;
|
||||
}
|
||||
auto index = (nextAdd_++) % QueueSize;
|
||||
return &queue_[index];
|
||||
}
|
||||
void CommitAdd() { ++pendingSends_; }
|
||||
|
||||
bool HavePendingSends() const { return pendingSends_.load() != 0; }
|
||||
ElementType* GetNextSendMessage()
|
||||
{
|
||||
auto index = (nextSend_++) % QueueSize;
|
||||
return &queue_[index];
|
||||
}
|
||||
void CommitSend() { --pendingSends_; }
|
||||
};
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
#include "rpc_connection.h"
|
||||
#include "serialization.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
static const int RpcVersion = 1;
|
||||
static RpcConnection Instance;
|
||||
|
||||
/*static*/ RpcConnection* RpcConnection::Create(const char* applicationId)
|
||||
{
|
||||
Instance.connection = BaseConnection::Create();
|
||||
StringCopy(Instance.appId, applicationId);
|
||||
return &Instance;
|
||||
}
|
||||
|
||||
/*static*/ void RpcConnection::Destroy(RpcConnection*& c)
|
||||
{
|
||||
c->Close();
|
||||
BaseConnection::Destroy(c->connection);
|
||||
c = nullptr;
|
||||
}
|
||||
|
||||
void RpcConnection::Open()
|
||||
{
|
||||
if (state == State::Connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == State::Disconnected) {
|
||||
if (connection->Open()) {
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == State::SentHandshake) {
|
||||
JsonDocument message;
|
||||
if (Read(message)) {
|
||||
auto cmd = GetStrMember(&message, "cmd");
|
||||
auto evt = GetStrMember(&message, "evt");
|
||||
if (cmd && evt && !strcmp(cmd, "DISPATCH") && !strcmp(evt, "READY")) {
|
||||
state = State::Connected;
|
||||
if (onConnect) {
|
||||
onConnect(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
sendFrame.opcode = Opcode::Handshake;
|
||||
sendFrame.length = (uint32_t)JsonWriteHandshakeObj(
|
||||
sendFrame.message, sizeof(sendFrame.message), RpcVersion, appId);
|
||||
|
||||
if (connection->Write(&sendFrame, sizeof(MessageFrameHeader) + sendFrame.length)) {
|
||||
state = State::SentHandshake;
|
||||
}
|
||||
else {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RpcConnection::Close()
|
||||
{
|
||||
if (onDisconnect && (state == State::Connected || state == State::SentHandshake)) {
|
||||
onDisconnect(lastErrorCode, lastErrorMessage);
|
||||
}
|
||||
connection->Close();
|
||||
state = State::Disconnected;
|
||||
}
|
||||
|
||||
bool RpcConnection::Write(const void* data, size_t length)
|
||||
{
|
||||
sendFrame.opcode = Opcode::Frame;
|
||||
memcpy(sendFrame.message, data, length);
|
||||
sendFrame.length = (uint32_t)length;
|
||||
if (!connection->Write(&sendFrame, sizeof(MessageFrameHeader) + length)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RpcConnection::Read(JsonDocument& message)
|
||||
{
|
||||
if (state != State::Connected && state != State::SentHandshake) {
|
||||
return false;
|
||||
}
|
||||
MessageFrame readFrame;
|
||||
for (;;) {
|
||||
bool didRead = connection->Read(&readFrame, sizeof(MessageFrameHeader));
|
||||
if (!didRead) {
|
||||
if (!connection->isOpen) {
|
||||
lastErrorCode = (int)ErrorCode::PipeClosed;
|
||||
StringCopy(lastErrorMessage, "Pipe closed");
|
||||
Close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (readFrame.length > 0) {
|
||||
didRead = connection->Read(readFrame.message, readFrame.length);
|
||||
if (!didRead) {
|
||||
lastErrorCode = (int)ErrorCode::ReadCorrupt;
|
||||
StringCopy(lastErrorMessage, "Partial data in frame");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
readFrame.message[readFrame.length] = 0;
|
||||
}
|
||||
|
||||
switch (readFrame.opcode) {
|
||||
case Opcode::Close: {
|
||||
message.ParseInsitu(readFrame.message);
|
||||
lastErrorCode = GetIntMember(&message, "code");
|
||||
StringCopy(lastErrorMessage, GetStrMember(&message, "message", ""));
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
case Opcode::Frame:
|
||||
message.ParseInsitu(readFrame.message);
|
||||
return true;
|
||||
case Opcode::Ping:
|
||||
readFrame.opcode = Opcode::Pong;
|
||||
if (!connection->Write(&readFrame, sizeof(MessageFrameHeader) + readFrame.length)) {
|
||||
Close();
|
||||
}
|
||||
break;
|
||||
case Opcode::Pong:
|
||||
break;
|
||||
case Opcode::Handshake:
|
||||
default:
|
||||
// something bad happened
|
||||
lastErrorCode = (int)ErrorCode::ReadCorrupt;
|
||||
StringCopy(lastErrorMessage, "Bad ipc frame");
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user