diff --git a/adapters/standalone.cpp b/adapters/standalone.cpp index 658ff2e7..a9940505 100644 --- a/adapters/standalone.cpp +++ b/adapters/standalone.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -175,6 +176,7 @@ int main(int argc, char* argv[]) { gamepad::init(); plugin::init(); library::init(); + discord::init(); if (!settings::headless) { ui::init(); windowInit(); @@ -251,6 +253,7 @@ int main(int argc, char* argv[]) { windowDestroy(); ui::destroy(); } + discord::destroy(); library::destroy(); midi::destroy(); audio::destroy(); diff --git a/include/discord.hpp b/include/discord.hpp new file mode 100644 index 00000000..6b74b623 --- /dev/null +++ b/include/discord.hpp @@ -0,0 +1,13 @@ +#pragma once +#include + +namespace rack { +namespace discord { + + +void init(); +void destroy(); + + +} // namespace discord +} // namespace rack diff --git a/include/settings.hpp b/include/settings.hpp index 91f7be7b..b0d979f7 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -78,6 +78,7 @@ extern std::vector cableColors; extern bool autoCheckUpdates; extern bool showTipsOnLaunch; extern int tipIndex; +extern bool discordUpdateActivity; enum ModuleBrowserSort { MODULE_BROWSER_SORT_UPDATED, MODULE_BROWSER_SORT_LAST_USED, diff --git a/src/discord.cpp b/src/discord.cpp new file mode 100644 index 00000000..efca8108 --- /dev/null +++ b/src/discord.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + + +namespace rack { +namespace discord { + + +static const char* CLIENT_ID = "878351961274060861"; + +static bool running = false; +static std::thread thread; +static std::mutex mutex; +static std::condition_variable cv; + + +static int sendJson(int fd, int32_t opcode, json_t* j) { + // Encode payload + char* json = json_dumps(j, 0); + if (!json) + return 1; + DEFER({free(json);}); + size_t len = strlen(json); + + // Send header + int32_t header[2] = {opcode, int32_t(len)}; + if (write(fd, header, sizeof(header)) != sizeof(header)) + return 1; + + // Send payload + if (write(fd, json, len) != ssize_t(len)) + return 1; + + return 0; +} + + +static json_t* receiveJson(int fd) { + // Receive header + int32_t header[2]; + if (read(fd, header, sizeof(header)) != sizeof(header)) + return NULL; + + // Receive payload + size_t len = header[1]; + char json[len]; + if (read(fd, json, len) != ssize_t(len)) + return NULL; + // DEBUG("Payload: %.*s", int(len), json); + + // Parse payload + json_t* j = json_loadb(json, len, 0, NULL); + return j; +} + + +static void run() { + system::setThreadName("Discord IPC"); + random::init(); + + // Open socket + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(fd); + if (fd < 0) { + WARN("Could not open Discord socket"); + return; + } + DEFER({close(fd);}); + + // Get socket path + const char* env = getenv("XDG_RUNTIME_DIR"); + if (!env) + env = getenv("TMPDIR"); + if (!env) + env = getenv("TMP"); + if (!env) + env = getenv("TEMP"); + if (!env) + env = "/tmp"; + std::string path = std::string() + env + "/discord-ipc-0"; + + // Connect to socket + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path.c_str()); + if (connect(fd, (struct sockaddr*) &addr, sizeof(addr))) { + // Fail silently since this just means Discord isn't open. + // WARN("Could not bind Discord socket"); + return; + } + + // Send handshake + json_t* handshakeJ = json_object(); + json_object_set(handshakeJ, "v", json_integer(1)); + json_object_set(handshakeJ, "client_id", json_string(CLIENT_ID)); + DEFER({json_decref(handshakeJ);}); + if (sendJson(fd, 0, handshakeJ)) { + WARN("Could not request Discord handshake"); + return; + } + + // Receive handshake response + json_t* handshakeResJ = receiveJson(fd); + if (!handshakeResJ) { + WARN("Could not receive Discord handshake response"); + return; + } + DEFER({json_decref(handshakeResJ);}); + + // Send activity + json_t* payloadJ = json_object(); + json_object_set(payloadJ, "cmd", json_string("SET_ACTIVITY")); + json_object_set(payloadJ, "nonce", json_string(std::to_string(random::u64()).c_str())); + { + json_t* argsJ = json_object(); + json_object_set(argsJ, "pid", json_integer(getpid())); + { + json_t* activityJ = json_object(); + { + json_t* timestampsJ = json_object(); + json_object_set(timestampsJ, "start", json_integer(system::getUnixTime())); + json_object_set(activityJ, "timestamps", timestampsJ); + } + json_object_set(argsJ, "activity", activityJ); + } + json_object_set(payloadJ, "args", argsJ); + } + DEFER({json_decref(payloadJ);}); + if (sendJson(fd, 1, payloadJ)) { + WARN("Could not set activity on Discord"); + return; + } + + // Receive activity response + json_t* payloadResJ = receiveJson(fd); + if (!payloadResJ) { + WARN("Could not receive Discord activity response"); + return; + } + DEFER({json_decref(payloadResJ);}); + + // Wait for destroy() + std::unique_lock lock(mutex); + cv.wait(lock, []() {return !running;}); + + // Ask Discord to disconnect + // json_t* disconnectJ = json_object(); + // DEFER({json_decref(disconnectJ);}); + // if (sendJson(fd, 2, disconnectJ)) { + // WARN("Could not disconnect from Discord"); + // return; + // } +} + + +void init() { + if (!settings::discordUpdateActivity) + return; + running = true; + thread = std::thread(run); +} + + +void destroy() { + { + std::lock_guard lock(mutex); + running = false; + cv.notify_all(); + } + if (thread.joinable()) + thread.join(); +} + + +} // namespace discord +} // namespace rack diff --git a/src/settings.cpp b/src/settings.cpp index 13e32740..35001dd2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -61,6 +61,7 @@ std::vector cableColors = { bool autoCheckUpdates = true; bool showTipsOnLaunch = true; int tipIndex = -1; +bool discordUpdateActivity = true; ModuleBrowserSort moduleBrowserSort = MODULE_BROWSER_SORT_UPDATED; float moduleBrowserZoom = -1.f; std::map> moduleWhitelist = {}; @@ -161,6 +162,9 @@ json_t* toJson() { json_object_set_new(rootJ, "tipIndex", json_integer(tipIndex)); + if (!discordUpdateActivity) + json_object_set_new(rootJ, "discordUpdateActivity", json_boolean(discordUpdateActivity)); + json_object_set_new(rootJ, "moduleBrowserSort", json_integer((int) moduleBrowserSort)); json_object_set_new(rootJ, "moduleBrowserZoom", json_real(moduleBrowserZoom)); @@ -333,6 +337,10 @@ void fromJson(json_t* rootJ) { if (tipIndexJ) tipIndex = json_integer_value(tipIndexJ); + json_t* discordUpdateActivityJ = json_object_get(rootJ, "discordUpdateActivity"); + if (discordUpdateActivityJ) + discordUpdateActivity = json_boolean_value(discordUpdateActivityJ); + json_t* moduleBrowserSortJ = json_object_get(rootJ, "moduleBrowserSort"); if (moduleBrowserSortJ) moduleBrowserSort = (ModuleBrowserSort) json_integer_value(moduleBrowserSortJ);