From 1b65e53da7f79541f844231ee8f5404197a7494d Mon Sep 17 00:00:00 2001 From: Chris Marsh Date: Tue, 11 Jul 2017 15:59:14 -0700 Subject: [PATCH] Rename some things, stub of async version, download rapidjson in cmake file --- .gitignore | 1 + CMakeLists.txt | 20 +++++ examples/simple/CMakeLists.txt | 4 +- examples/simpleSync/CMakeLists.txt | 3 + examples/simpleSync/simpleSync.c | 90 +++++++++++++++++++ include/discord-rpc.h | 2 +- src/CMakeLists.txt | 3 +- src/connection.h | 3 +- src/connection_win.cpp | 133 +++++++++++++++++++++++++++++ src/connection_win_sync.cpp | 44 +++++++++- src/discord-rpc-simple.cpp | 2 +- src/discord-rpc.cpp | 35 +++++--- 12 files changed, 319 insertions(+), 21 deletions(-) create mode 100644 examples/simpleSync/CMakeLists.txt create mode 100644 examples/simpleSync/simpleSync.c create mode 100644 src/connection_win.cpp diff --git a/.gitignore b/.gitignore index cf08f57..6f949c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /build*/ /.vscode/ +/thirdparty/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 289bee7..786e8a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,26 @@ cmake_minimum_required (VERSION 3.7.0) project (DiscordRPCExample) +execute_process( + COMMAND mkdir ${CMAKE_SOURCE_DIR}/thirdparty + ERROR_QUIET +) + +find_file(RAPIDJSON NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_SOURCE_DIR}/thirdparty) + +if (NOT RAPIDJSON) + message("no rapidjson, download") + set(RJ_TAR_FILE ${CMAKE_SOURCE_DIR}/thirdparty/v1.1.0.tar.gz) + file(DOWNLOAD https://github.com/miloyip/rapidjson/archive/v1.1.0.tar.gz ${RJ_TAR_FILE}) + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xzf ${RJ_TAR_FILE} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/thirdparty + ) + file(REMOVE ${RJ_TAR_FILE}) +endif(NOT RAPIDJSON) + add_subdirectory(src) add_subdirectory(examples/simple) +add_subdirectory(examples/simpleSync) add_subdirectory(examples/simplest) + diff --git a/examples/simple/CMakeLists.txt b/examples/simple/CMakeLists.txt index e891682..d045889 100644 --- a/examples/simple/CMakeLists.txt +++ b/examples/simple/CMakeLists.txt @@ -1,3 +1,3 @@ include_directories(${PROJECT_SOURCE_DIR}/include) -add_executable(simple-client simple.c) -target_link_libraries(simple-client discord-rpc) +add_executable(simple-async-client simple.c) +target_link_libraries(simple-async-client discord-rpc) diff --git a/examples/simpleSync/CMakeLists.txt b/examples/simpleSync/CMakeLists.txt new file mode 100644 index 0000000..3f0bb21 --- /dev/null +++ b/examples/simpleSync/CMakeLists.txt @@ -0,0 +1,3 @@ +include_directories(${PROJECT_SOURCE_DIR}/include) +add_executable(simple-client simpleSync.c) +target_link_libraries(simple-client discord-rpc-sync) diff --git a/examples/simpleSync/simpleSync.c b/examples/simpleSync/simpleSync.c new file mode 100644 index 0000000..05ef986 --- /dev/null +++ b/examples/simpleSync/simpleSync.c @@ -0,0 +1,90 @@ +/* + This is a simple example in C of using the rich presence API asyncronously. +*/ + +#define _CRT_SECURE_NO_WARNINGS /* thanks Microsoft */ + +#include +#include +#include +#include + +#include "discord-rpc.h" + +static const char* APPLICATION_ID = "12345678910"; +static int FrustrationLevel = 0; + +static void updateDiscordPresence() { + char buffer[256]; + DiscordRichPresence discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + discordPresence.state = "West of House"; + sprintf(buffer, "Frustration level: %d", FrustrationLevel); + discordPresence.details = buffer; + Discord_UpdatePresence(&discordPresence); +} + +static void handleDiscordReady() { + printf("\nDiscord: ready\n"); +} + +static void handleDiscordDisconnected() { + printf("\nDiscord: disconnected\n"); +} + +static void handleDiscordWantsPresence() { + printf("\nDiscord: requests presence\n"); + updateDiscordPresence(); +} + +static int prompt(char* line, size_t size) { + int res; + char* nl; + printf("\n> "); + fflush(stdout); + res = fgets(line, size, stdin) ? 1 : 0; + line[size - 1] = 0; + nl = strchr(line, '\n'); + if (nl) { + *nl = 0; + } + return res; +} + +static void gameLoop() { + char line[512]; + char* space; + + printf("You are standing in an open field west of a white house.\n"); + while (prompt(line, sizeof(line))) { + if (time(NULL) & 1) { + printf("I don't understand that.\n"); + } else { + space = strchr(line, ' '); + if (space) { + *space = 0; + } + printf("I don't know the word \"%s\".\n", line); + } + + ++FrustrationLevel; + + updateDiscordPresence(); + Discord_Update(); + } +} + +int main() { + DiscordEventHandlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.ready = handleDiscordReady; + handlers.disconnected = handleDiscordDisconnected; + handlers.wantsPresence = handleDiscordWantsPresence; + Discord_Initialize(APPLICATION_ID, &handlers); + + gameLoop(); + + Discord_Shutdown(); + return 0; +} + diff --git a/include/discord-rpc.h b/include/discord-rpc.h index 5d1cf47..4f6e900 100644 --- a/include/discord-rpc.h +++ b/include/discord-rpc.h @@ -25,7 +25,7 @@ typedef struct { typedef struct { void (*ready)(); - void (*disconnected)(); + void (*disconnected)(int errorCode, const char* message); void (*wantsPresence)(); void (*joinGame)(const char* joinSecret); void (*spectateGame)(const char* spectateSecret); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 93eeaaf..a242eb5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,5 +5,6 @@ add_library(discord-rpc-simple STATIC ${PROJECT_SOURCE_DIR}/include/discord-rpc. set(BASE_RPC_SRC ${PROJECT_SOURCE_DIR}/include/discord-rpc.h discord-rpc.cpp yolojson.h connection.h) if(WIN32) - add_library(discord-rpc STATIC ${BASE_RPC_SRC} connection_win_sync.cpp) + add_library(discord-rpc-sync STATIC ${BASE_RPC_SRC} connection_win_sync.cpp) + add_library(discord-rpc STATIC ${BASE_RPC_SRC} connection_win.cpp) endif(WIN32) diff --git a/src/connection.h b/src/connection.h index 6f57ddf..f7814d3 100644 --- a/src/connection.h +++ b/src/connection.h @@ -18,7 +18,7 @@ struct RpcMessageFrame { struct RpcConnection { void (*onConnect)() = nullptr; - void (*onDisconnect)() = nullptr; + void (*onDisconnect)(int errorcode, const char* message) = nullptr; char appId[64]; static RpcConnection* Create(const char* applicationId); @@ -26,6 +26,7 @@ struct RpcConnection { void Open(); void Close(); void Write(const void* data, size_t length); + RpcMessageFrame* Read(); RpcMessageFrame* GetNextFrame(); void WriteFrame(RpcMessageFrame* frame); diff --git a/src/connection_win.cpp b/src/connection_win.cpp new file mode 100644 index 0000000..d7160fc --- /dev/null +++ b/src/connection_win.cpp @@ -0,0 +1,133 @@ +#include "connection.h" + +#include + +#define WIN32_LEAN_AND_MEAN +#define NOMCX +#define NOSERVICE +#define NOIME +#include + +#include "yolojson.h" + +const int RpcVersion = 1; +const int NumFrames = 3; +static int LastErrorCode = 0; +static const char* LastErrorMessage = ""; + +struct WinRpcConnection : public RpcConnection { + HANDLE pipe{INVALID_HANDLE_VALUE}; + RpcMessageFrame frames[NumFrames]; + int nextFrame{0}; + int lastErrorCode{0}; + char lastErrorMessage[1024]; + + void HandleError(RpcMessageFrame* frame) { + if (frame->opcode == OPCODE::CLOSE) { + lastErrorCode = 1; // todo + StringCopy(lastErrorMessage, frame->message, sizeof(lastErrorMessage)); + printf("got a close message: %d: %s\n", lastErrorCode, lastErrorMessage); + } + } +}; + +static const wchar_t* PipeName = L"\\\\?\\pipe\\discord-ipc"; + +/*static*/ RpcConnection* RpcConnection::Create(const char* applicationId) +{ + auto connection = new WinRpcConnection; + StringCopy(connection->appId, applicationId, sizeof(connection->appId)); + return connection; +} + +/*static*/ void RpcConnection::Destroy(RpcConnection*& c) +{ + auto self = reinterpret_cast(c); + delete self; + c = nullptr; +} + +void RpcConnection::Open() +{ + auto self = reinterpret_cast(this); + for (;;) { + self->pipe = ::CreateFileW(PipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); + if (self->pipe != INVALID_HANDLE_VALUE) { + break; + } + + if (GetLastError() != ERROR_PIPE_BUSY) { + printf("Could not open pipe. Error: %d\n", GetLastError()); + return; + } + + if (!WaitNamedPipeW(PipeName, 10000)) { + printf("Could not open pipe: 10 second wait timed out.\n"); + return; + } + } + + RpcMessageFrame* frame = GetNextFrame(); + frame->opcode = OPCODE::HANDSHAKE; + char* msg = frame->message; + JsonWriteHandshakeObj(msg, RpcVersion, appId); + frame->length = msg - frame->message; + WriteFrame(frame); + + if (self->onConnect) { + self->onConnect(); + } +} + +void RpcConnection::Close() +{ + auto self = reinterpret_cast(this); + ::CloseHandle(self->pipe); + self->pipe = INVALID_HANDLE_VALUE; + if (self->onDisconnect) { + self->onDisconnect(LastErrorCode, LastErrorMessage); + LastErrorCode = 0; + LastErrorMessage = ""; + } +} + +void RpcConnection::Write(const void* data, size_t length) +{ + auto self = reinterpret_cast(this); + const int retries = 3; + for (int i = 0; i < retries; ++i) { + if (self->pipe == INVALID_HANDLE_VALUE) { + self->Open(); + if (self->pipe == INVALID_HANDLE_VALUE) { + break; + } + } + BOOL success = ::WriteFile(self->pipe, data, length, nullptr, nullptr); + if (success) { + break; + } + LastErrorCode = -1; + LastErrorMessage = "Pipe closed"; + self->Close(); + } +} + +RpcMessageFrame* RpcConnection::Read() +{ + // todo + return nullptr; +} + +RpcMessageFrame* RpcConnection::GetNextFrame() +{ + auto self = reinterpret_cast(this); + auto result = &(self->frames[self->nextFrame]); + self->nextFrame = (self->nextFrame + 1) % NumFrames; + return result; +} + +void RpcConnection::WriteFrame(RpcMessageFrame* frame) +{ + auto self = reinterpret_cast(this); + self->Write(frame, 8 + frame->length); +} diff --git a/src/connection_win_sync.cpp b/src/connection_win_sync.cpp index c712dc0..490f7a2 100644 --- a/src/connection_win_sync.cpp +++ b/src/connection_win_sync.cpp @@ -11,12 +11,23 @@ #include "yolojson.h" const int RpcVersion = 1; -const int NumFrames = 3; +const int NumFrames = 4; struct WinRpcConnection : public RpcConnection { HANDLE pipe{INVALID_HANDLE_VALUE}; + RpcMessageFrame readFrame; RpcMessageFrame frames[NumFrames]; int nextFrame{0}; + int lastErrorCode{0}; + char lastErrorMessage[1024]; + + void HandleError(RpcMessageFrame* frame) { + if (frame->opcode == OPCODE::CLOSE) { + lastErrorCode = 1; // todo + StringCopy(lastErrorMessage, frame->message, sizeof(lastErrorMessage)); + printf("got a close message: %d: %s\n", lastErrorCode, lastErrorMessage); + } + } }; static const wchar_t* PipeName = L"\\\\?\\pipe\\discord-ipc"; @@ -73,7 +84,9 @@ void RpcConnection::Close() ::CloseHandle(self->pipe); self->pipe = INVALID_HANDLE_VALUE; if (self->onDisconnect) { - self->onDisconnect(); + self->onDisconnect(self->lastErrorCode, self->lastErrorMessage); + self->lastErrorCode = 0; + self->lastErrorMessage[0] = 0; } } @@ -92,10 +105,37 @@ void RpcConnection::Write(const void* data, size_t length) if (success) { break; } + + RpcMessageFrame* frame = self->Read(); + if (frame) { + self->HandleError(frame); + } + self->Close(); } } +RpcMessageFrame* RpcConnection::Read() +{ + auto self = reinterpret_cast(this); + DWORD bytesAvailable = 0; + if (::PeekNamedPipe(self->pipe, nullptr, 0, nullptr, &bytesAvailable, nullptr) && bytesAvailable > 8) { + if (::ReadFile(self->pipe, &self->readFrame, 8, nullptr, nullptr) != TRUE) { + return nullptr; + } + + if (self->readFrame.length > 0) { + if (::ReadFile(self->pipe, &self->readFrame.message, self->readFrame.length, nullptr, nullptr) != TRUE) { + return nullptr; + } + self->readFrame.message[self->readFrame.length] = 0; + } + + return &self->readFrame; + } + return nullptr; +} + RpcMessageFrame* RpcConnection::GetNextFrame() { auto self = reinterpret_cast(this); diff --git a/src/discord-rpc-simple.cpp b/src/discord-rpc-simple.cpp index 538202a..dfce211 100644 --- a/src/discord-rpc-simple.cpp +++ b/src/discord-rpc-simple.cpp @@ -275,7 +275,7 @@ void ConnectionClose() CloseHandle(PipeHandle); PipeHandle = INVALID_HANDLE_VALUE; if (Handlers.disconnected) { - Handlers.disconnected(); + Handlers.disconnected(0, ""); } } diff --git a/src/discord-rpc.cpp b/src/discord-rpc.cpp index 45e9b4f..64b9fc8 100644 --- a/src/discord-rpc.cpp +++ b/src/discord-rpc.cpp @@ -3,11 +3,15 @@ #include "connection.h" #include "yolojson.h" +#include + static RpcConnection* MyConnection = nullptr; static char ApplicationId[64]{}; static DiscordEventHandlers Handlers{}; -static bool wasJustConnected = false; -static bool wasJustDisconnected = false; +static bool WasJustConnected = false; +static bool WasJustDisconnected = false; +static int LastErrorCode = 0; +static const char* LastErrorMessage = ""; extern "C" void Discord_Initialize(const char* applicationId, DiscordEventHandlers* handlers) { @@ -19,8 +23,12 @@ extern "C" void Discord_Initialize(const char* applicationId, DiscordEventHandle } MyConnection = RpcConnection::Create(applicationId); - MyConnection->onConnect = []() { wasJustConnected = true; }; - MyConnection->onDisconnect = []() { wasJustDisconnected = true; }; + MyConnection->onConnect = []() { WasJustConnected = true; }; + MyConnection->onDisconnect = [](int errorCode, const char* message) { + LastErrorCode = errorCode; + LastErrorMessage = message; + WasJustDisconnected = true; + }; MyConnection->Open(); } @@ -43,17 +51,18 @@ extern "C" void Discord_UpdatePresence(const DiscordRichPresence* presence) extern "C" void Discord_Update() { - // check for messages - // todo - - // fire callbacks - if (wasJustDisconnected && Handlers.disconnected) { - wasJustDisconnected = false; - Handlers.disconnected(); + while (auto frame = MyConnection->Read()) { + printf("got a message %d, %d, %s\n", frame->opcode, frame->length, frame->message); } - if (wasJustConnected && Handlers.ready) { - wasJustConnected = false; + // fire callbacks + if (WasJustDisconnected && Handlers.disconnected) { + WasJustDisconnected = false; + Handlers.disconnected(LastErrorCode, LastErrorMessage); + } + + if (WasJustConnected && Handlers.ready) { + WasJustConnected = false; Handlers.ready(); } }