You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

205 lines
4.5KB

  1. #include <discord.hpp>
  2. #include <system.hpp>
  3. #include <random.hpp>
  4. #include <settings.hpp>
  5. #include <thread>
  6. #include <mutex>
  7. #include <condition_variable>
  8. #if defined ARCH_LIN || defined ARCH_MAC
  9. #include <unistd.h>
  10. #include <sys/types.h>
  11. #include <sys/socket.h>
  12. #include <sys/un.h>
  13. #endif
  14. #if defined ARCH_WIN
  15. #include <fcntl.h>
  16. #endif
  17. #include <jansson.h>
  18. namespace rack {
  19. namespace discord {
  20. static const char* CLIENT_ID = "878351961274060861";
  21. static bool running = false;
  22. static std::thread thread;
  23. static std::mutex mutex;
  24. static std::condition_variable cv;
  25. static int sendJson(int fd, int32_t opcode, json_t* j) {
  26. // Encode payload
  27. char* json = json_dumps(j, 0);
  28. if (!json)
  29. return 1;
  30. DEFER({free(json);});
  31. size_t len = strlen(json);
  32. // Send header
  33. int32_t header[2] = {opcode, int32_t(len)};
  34. if (write(fd, header, sizeof(header)) != sizeof(header))
  35. return 1;
  36. // Send payload
  37. if (write(fd, json, len) != ssize_t(len))
  38. return 1;
  39. return 0;
  40. }
  41. static json_t* receiveJson(int fd) {
  42. // Receive header
  43. int32_t header[2];
  44. if (read(fd, header, sizeof(header)) != sizeof(header))
  45. return NULL;
  46. // Receive payload
  47. size_t len = header[1];
  48. char json[len];
  49. if (read(fd, json, len) != ssize_t(len))
  50. return NULL;
  51. // DEBUG("Payload: %.*s", int(len), json);
  52. // Parse payload
  53. json_t* j = json_loadb(json, len, 0, NULL);
  54. return j;
  55. }
  56. static void run() {
  57. system::setThreadName("Discord IPC");
  58. random::init();
  59. // Open socket
  60. #if defined ARCH_LIN || defined ARCH_MAC
  61. int fd = socket(AF_UNIX, SOCK_STREAM, 0);
  62. if (fd < 0) {
  63. WARN("Could not open Discord socket");
  64. return;
  65. }
  66. DEFER({close(fd);});
  67. // Get socket path
  68. const char* dir = getenv("XDG_RUNTIME_DIR");
  69. if (!dir)
  70. dir = getenv("TMPDIR");
  71. if (!dir)
  72. dir = getenv("TMP");
  73. if (!dir)
  74. dir = getenv("TEMP");
  75. if (!dir)
  76. dir = "/tmp";
  77. // Connect to socket
  78. struct sockaddr_un addr;
  79. addr.sun_family = AF_UNIX;
  80. snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/discord-ipc-0", dir);
  81. if (connect(fd, (struct sockaddr*) &addr, sizeof(addr))) {
  82. // Fail silently since this just means Discord isn't open.
  83. // WARN("Could not bind Discord socket");
  84. return;
  85. }
  86. #endif
  87. #if defined ARCH_WIN
  88. const char* path = "\\\\?\\pipe\\discord-ipc-0";
  89. int fd = open(path, O_RDWR | O_APPEND);
  90. if (fd < 0) {
  91. // Fail silently since this just means Discord isn't open.
  92. // WARN("Could not open Discord socket");
  93. return;
  94. }
  95. DEFER({close(fd);});
  96. #endif
  97. // Send handshake
  98. json_t* handshakeJ = json_object();
  99. json_object_set(handshakeJ, "v", json_integer(1));
  100. json_object_set(handshakeJ, "client_id", json_string(CLIENT_ID));
  101. DEFER({json_decref(handshakeJ);});
  102. if (sendJson(fd, 0, handshakeJ)) {
  103. WARN("Could not request Discord handshake");
  104. return;
  105. }
  106. // Receive handshake response
  107. json_t* handshakeResJ = receiveJson(fd);
  108. if (!handshakeResJ) {
  109. WARN("Could not receive Discord handshake response");
  110. return;
  111. }
  112. DEFER({json_decref(handshakeResJ);});
  113. // Send activity
  114. json_t* payloadJ = json_object();
  115. json_object_set(payloadJ, "cmd", json_string("SET_ACTIVITY"));
  116. json_object_set(payloadJ, "nonce", json_string(std::to_string(random::u64()).c_str()));
  117. {
  118. json_t* argsJ = json_object();
  119. json_object_set(argsJ, "pid", json_integer(getpid()));
  120. {
  121. json_t* activityJ = json_object();
  122. {
  123. json_t* timestampsJ = json_object();
  124. json_object_set(timestampsJ, "start", json_integer(system::getUnixTime()));
  125. json_object_set(activityJ, "timestamps", timestampsJ);
  126. }
  127. json_object_set(argsJ, "activity", activityJ);
  128. }
  129. json_object_set(payloadJ, "args", argsJ);
  130. }
  131. DEFER({json_decref(payloadJ);});
  132. if (sendJson(fd, 1, payloadJ)) {
  133. WARN("Could not set activity on Discord");
  134. return;
  135. }
  136. // Receive activity response
  137. json_t* payloadResJ = receiveJson(fd);
  138. if (!payloadResJ) {
  139. WARN("Could not receive Discord activity response");
  140. return;
  141. }
  142. DEFER({json_decref(payloadResJ);});
  143. // Wait for destroy()
  144. std::unique_lock<std::mutex> lock(mutex);
  145. cv.wait(lock, []() {return !running;});
  146. // Ask Discord to disconnect
  147. // json_t* disconnectJ = json_object();
  148. // DEFER({json_decref(disconnectJ);});
  149. // if (sendJson(fd, 2, disconnectJ)) {
  150. // WARN("Could not disconnect from Discord");
  151. // return;
  152. // }
  153. }
  154. void init() {
  155. if (!settings::discordUpdateActivity)
  156. return;
  157. running = true;
  158. thread = std::thread(run);
  159. }
  160. void destroy() {
  161. {
  162. std::lock_guard<std::mutex> lock(mutex);
  163. running = false;
  164. cv.notify_all();
  165. }
  166. if (thread.joinable())
  167. thread.join();
  168. }
  169. } // namespace discord
  170. } // namespace rack