Discord rich presence (#4500)
* WIP RPC support * Might have windows support now, don't peek * Windows support * RichPresence: __SWITCH__ => DISABLE_RPC (for MOBILE_STK support) * RichPresence: Handle JSON strings according to spec, support for addons icon * RichPresence: use translated difficulty name * RichPresence: disable when client_id=-1 * RichPresence: thread connection, show server name on RPC * RichPresence: destroy on close * RichPresence: don't compile methods at all if DISABLE_RPC * RichPresence: fix windows compile (untested) * RichPresence: fix for mac * RichPresence: Linux needs MSG_NOSIGNAL still * RichPresence: fix memory leaks, don't spam update while not connected * RichPresence: free thread on terminate * RichPresence: handle initial registration * RichPresence: fix compiler warning
This commit is contained in:
parent
8daf149895
commit
0dd3c62a43
@ -6297,3 +6297,7 @@ msgstr "If you need more stability, consider using the stable version: %s"
|
|||||||
#: supertuxkart.appdata.xml:44
|
#: supertuxkart.appdata.xml:44
|
||||||
msgid "SuperTuxKart Team"
|
msgid "SuperTuxKart Team"
|
||||||
msgstr "SuperTuxKart Team"
|
msgstr "SuperTuxKart Team"
|
||||||
|
|
||||||
|
#: rich_presence.cpp:296
|
||||||
|
msgid "Getting ready to race"
|
||||||
|
msgstr "Getting ready to race"
|
||||||
|
@ -42,6 +42,49 @@ namespace HardwareStats
|
|||||||
m_data ="{";
|
m_data ="{";
|
||||||
} // Constructor
|
} // Constructor
|
||||||
|
|
||||||
|
const std::string sanitize(const std::string &value)
|
||||||
|
{
|
||||||
|
// Really confusing. Basically converts between utf8 and wide and irrlicht strings and std strings
|
||||||
|
std::wstring wide = StringUtils::utf8ToWide(value).c_str();
|
||||||
|
std::string normalized = StringUtils::wideToUtf8(sanitize(wide).c_str()).c_str();
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::wstring sanitize(std::wstring value)
|
||||||
|
{
|
||||||
|
// A string is a sequence of Unicode code points wrapped with quotation marks (U+0022). All code points may
|
||||||
|
// be placed within the quotation marks except for the code points that must be escaped: quotation mark
|
||||||
|
// (U+0022), reverse solidus (U+005C), and the control characters U+0000 to U+001F. There are two-character
|
||||||
|
// escape sequence representations of some characters.
|
||||||
|
|
||||||
|
wchar_t temp[7] = {0};
|
||||||
|
for(size_t i = 0; i < value.size(); ++i)
|
||||||
|
{
|
||||||
|
if (value[i] <= 0x1f)
|
||||||
|
{
|
||||||
|
swprintf(temp, sizeof(temp) / sizeof(wchar_t), L"\\u%04x", value[i]);
|
||||||
|
std::wstring suffix = value.substr(i + 1);
|
||||||
|
value = value.substr(0, i);
|
||||||
|
value.append(temp);
|
||||||
|
value.append(suffix);
|
||||||
|
i += 5; // \u0000 = 6 chars, but we're replacing one so 5
|
||||||
|
}
|
||||||
|
else if (value[i] == '"' || value[i] == '\\')
|
||||||
|
{
|
||||||
|
char escaped = value[i];
|
||||||
|
std::wstring suffix = value.substr(i + 1);
|
||||||
|
value = value.substr(0, i);
|
||||||
|
value.push_back('\\');
|
||||||
|
value.push_back(escaped);
|
||||||
|
value.append(suffix);
|
||||||
|
// Skip the added solidus
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
/** Adds a key-value pair to the json string. */
|
/** Adds a key-value pair to the json string. */
|
||||||
template <typename C>
|
template <typename C>
|
||||||
@ -49,7 +92,7 @@ namespace HardwareStats
|
|||||||
{
|
{
|
||||||
if(m_data.size()>1) // more than '{'
|
if(m_data.size()>1) // more than '{'
|
||||||
m_data += ",";
|
m_data += ",";
|
||||||
m_data += "\""+key+"\":"+StringUtils::toString(value);
|
m_data += "\""+sanitize(key)+"\":"+StringUtils::toString(value);
|
||||||
} // add
|
} // add
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
/** Specialisation for adding string values. String values in
|
/** Specialisation for adding string values. String values in
|
||||||
@ -58,7 +101,7 @@ namespace HardwareStats
|
|||||||
{
|
{
|
||||||
if(m_data.size()>1) // more than '{'
|
if(m_data.size()>1) // more than '{'
|
||||||
m_data += ",";
|
m_data += ",";
|
||||||
m_data += "\""+key+"\":\""+StringUtils::toString(value)+"\"";
|
m_data += "\""+sanitize(key)+"\":\""+StringUtils::toString(sanitize(value))+"\"";
|
||||||
} // add
|
} // add
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
/** Specialisation for adding character pointers. String values in
|
/** Specialisation for adding character pointers. String values in
|
||||||
@ -67,7 +110,7 @@ namespace HardwareStats
|
|||||||
{
|
{
|
||||||
if(m_data.size()>1) // more than '{'
|
if(m_data.size()>1) // more than '{'
|
||||||
m_data += ",";
|
m_data += ",";
|
||||||
m_data += "\""+key+"\":\""+StringUtils::toString(s)+"\"";
|
m_data += "\""+sanitize(key)+"\":\""+StringUtils::toString(sanitize(s))+"\"";
|
||||||
} // add
|
} // add
|
||||||
// --------------------------------------------------------------------
|
// --------------------------------------------------------------------
|
||||||
void finish()
|
void finish()
|
||||||
|
@ -1152,6 +1152,14 @@ namespace UserConfigParams
|
|||||||
PARAM_DEFAULT( StringUserConfigParam("all", "last_track_group",
|
PARAM_DEFAULT( StringUserConfigParam("all", "last_track_group",
|
||||||
"Last selected track group") );
|
"Last selected track group") );
|
||||||
|
|
||||||
|
PARAM_PREFIX StringUserConfigParam m_discord_client_id
|
||||||
|
PARAM_DEFAULT( StringUserConfigParam("817760324983324753", "discord_client_id",
|
||||||
|
"Discord Client ID (Set to -1 to disable)") );
|
||||||
|
|
||||||
|
PARAM_PREFIX BoolUserConfigParam m_rich_presence_debug
|
||||||
|
PARAM_DEFAULT( BoolUserConfigParam(false, "rich_presence_debug",
|
||||||
|
"If debug logging should be enabled for rich presence") );
|
||||||
|
|
||||||
PARAM_PREFIX StringUserConfigParam m_skin_file
|
PARAM_PREFIX StringUserConfigParam m_skin_file
|
||||||
PARAM_DEFAULT( StringUserConfigParam("peach", "skin_name",
|
PARAM_DEFAULT( StringUserConfigParam("peach", "skin_name",
|
||||||
"Name of the skin to use") );
|
"Name of the skin to use") );
|
||||||
|
495
src/io/rich_presence.cpp
Normal file
495
src/io/rich_presence.cpp
Normal file
@ -0,0 +1,495 @@
|
|||||||
|
#include "utils/time.hpp"
|
||||||
|
#include "utils/string_utils.hpp"
|
||||||
|
#include "race/race_manager.hpp"
|
||||||
|
#include "io/rich_presence.hpp"
|
||||||
|
#include "config/player_manager.hpp"
|
||||||
|
#include "config/player_profile.hpp"
|
||||||
|
#include "modes/world.hpp"
|
||||||
|
#include "config/hardware_stats.hpp"
|
||||||
|
#include "config/user_config.hpp"
|
||||||
|
#include "tracks/track_manager.hpp"
|
||||||
|
#include "tracks/track.hpp"
|
||||||
|
#include "karts/abstract_kart.hpp"
|
||||||
|
#include "karts/kart_properties.hpp"
|
||||||
|
#include "utils/translation.hpp"
|
||||||
|
#include "network/protocols/client_lobby.hpp"
|
||||||
|
#include "network/protocols/lobby_protocol.hpp"
|
||||||
|
#include "network/server.hpp"
|
||||||
|
|
||||||
|
#include <locale>
|
||||||
|
#include <codecvt>
|
||||||
|
|
||||||
|
#if defined(__SWITCH__) || defined(MOBILE_STK) || defined(SERVER_ONLY)
|
||||||
|
#define DISABLE_RPC
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(WIN32) && !defined(DISABLE_RPC)
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#elif defined(WIN32)
|
||||||
|
#include <process.h>
|
||||||
|
#include <fileapi.h>
|
||||||
|
#include <namedpipeapi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace RichPresenceNS {
|
||||||
|
RichPresence* g_rich_presence = nullptr;
|
||||||
|
|
||||||
|
RichPresence* RichPresence::get() {
|
||||||
|
if (g_rich_presence == nullptr)
|
||||||
|
{
|
||||||
|
g_rich_presence = new RichPresence();
|
||||||
|
}
|
||||||
|
return g_rich_presence;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RichPresence::destroy() {
|
||||||
|
if (g_rich_presence != nullptr)
|
||||||
|
{
|
||||||
|
delete g_rich_presence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RichPresence::RichPresence() : m_connected(false), m_ready(false), m_last(0),
|
||||||
|
#ifdef WIN32
|
||||||
|
m_socket(INVALID_HANDLE_VALUE),
|
||||||
|
#else
|
||||||
|
m_socket(-1),
|
||||||
|
#endif
|
||||||
|
m_thread(nullptr) {
|
||||||
|
doConnect();
|
||||||
|
}
|
||||||
|
RichPresence::~RichPresence() {
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RichPresence::terminate() {
|
||||||
|
#ifndef DISABLE_RPC
|
||||||
|
#ifdef WIN32
|
||||||
|
#define UNCLEAN m_socket != INVALID_HANDLE_VALUE
|
||||||
|
#else
|
||||||
|
#define UNCLEAN m_socket != -1
|
||||||
|
#endif
|
||||||
|
if (m_connected || UNCLEAN)
|
||||||
|
{
|
||||||
|
if (UNCLEAN && !m_connected)
|
||||||
|
Log::fatal("RichPresence", "RichPresence terminated uncleanly! Socket is %d", m_socket);
|
||||||
|
#ifndef WIN32
|
||||||
|
close(m_socket);
|
||||||
|
m_socket = -1;
|
||||||
|
#else
|
||||||
|
CloseHandle(m_socket);
|
||||||
|
m_socket = INVALID_HANDLE_VALUE;
|
||||||
|
#endif
|
||||||
|
m_connected = false;
|
||||||
|
m_ready = false;
|
||||||
|
}
|
||||||
|
if(m_thread != nullptr && STKProcess::getType() == PT_MAIN)
|
||||||
|
{
|
||||||
|
m_thread->join();
|
||||||
|
delete m_thread;
|
||||||
|
m_thread = nullptr;
|
||||||
|
}
|
||||||
|
#endif // DISABLE_RPC
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RichPresence::doConnect() {
|
||||||
|
#ifndef DISABLE_RPC
|
||||||
|
if (std::string(UserConfigParams::m_discord_client_id) == "-1")
|
||||||
|
return false;
|
||||||
|
#ifndef DISABLE_RPC
|
||||||
|
// Just in case we're retrying or something:
|
||||||
|
terminate();
|
||||||
|
#if !defined(WIN32) && defined(AF_UNIX)
|
||||||
|
m_socket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (m_socket < 0)
|
||||||
|
{
|
||||||
|
if (UserConfigParams::m_rich_presence_debug)
|
||||||
|
perror("Couldn't open a Unix socket!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#ifdef SO_NOSIGPIPE
|
||||||
|
const int set = 1;
|
||||||
|
setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, &set, sizeof(set));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Discord tries these env vars in order:
|
||||||
|
char* env;
|
||||||
|
std::string basePath = "";
|
||||||
|
#define TRY_ENV(path) env = std::getenv(path); \
|
||||||
|
if (env != nullptr) \
|
||||||
|
{\
|
||||||
|
basePath = env; \
|
||||||
|
goto completed; \
|
||||||
|
}
|
||||||
|
|
||||||
|
TRY_ENV("XDG_RUNTIME_DIR")
|
||||||
|
TRY_ENV("TMPDIR")
|
||||||
|
TRY_ENV("TMP")
|
||||||
|
TRY_ENV("TEMP")
|
||||||
|
#undef TRY_ENV
|
||||||
|
// Falls back to /tmp
|
||||||
|
basePath = "/tmp";
|
||||||
|
completed:
|
||||||
|
basePath = basePath + "/";
|
||||||
|
#elif defined(WIN32)
|
||||||
|
// Windows uses named pipes
|
||||||
|
std::string basePath = "\\\\?\\pipe\\";
|
||||||
|
#endif
|
||||||
|
// Discord will only bind up to socket 9
|
||||||
|
for (int i = 0; i < 10; ++i)
|
||||||
|
{
|
||||||
|
if (tryConnect(basePath + "discord-ipc-" + StringUtils::toString(i)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_connected)
|
||||||
|
{
|
||||||
|
if (UserConfigParams::m_rich_presence_debug)
|
||||||
|
Log::info("RichPresence", "Connection opened with Discord!");
|
||||||
|
m_thread = new std::thread(finishConnection, this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Force cleanup:
|
||||||
|
m_connected = true;
|
||||||
|
terminate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
#endif // DISABLE_RPC
|
||||||
|
}
|
||||||
|
|
||||||
|
void RichPresence::readData() {
|
||||||
|
#ifndef DISABLE_RPC
|
||||||
|
size_t baseLength = sizeof(int32_t) * 2;
|
||||||
|
struct discordPacket* basePacket = (struct discordPacket*) malloc(baseLength);
|
||||||
|
#ifdef WIN32
|
||||||
|
DWORD read;
|
||||||
|
if (!ReadFile(m_socket, basePacket, baseLength, &read, NULL))
|
||||||
|
{
|
||||||
|
Log::error("RichPresence", "Couldn't read from pipe! Error %x", GetLastError());
|
||||||
|
free(basePacket);
|
||||||
|
terminate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int read = recv(m_socket, basePacket, baseLength, 0);
|
||||||
|
if (read == -1)
|
||||||
|
{
|
||||||
|
if (UserConfigParams::m_rich_presence_debug)
|
||||||
|
perror("Couldn't read data from socket!");
|
||||||
|
terminate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// Add one char so we can printf easy
|
||||||
|
struct discordPacket* packet = (struct discordPacket*)
|
||||||
|
malloc(baseLength + basePacket->length + sizeof(char));
|
||||||
|
// Copy over length and opcode from base packet
|
||||||
|
memcpy(packet, basePacket, baseLength);
|
||||||
|
free(basePacket);
|
||||||
|
#ifdef WIN32
|
||||||
|
if (!ReadFile(m_socket, packet->data, packet->length, &read, NULL))
|
||||||
|
{
|
||||||
|
Log::error("RichPresence", "Couldn't read from pipe! Error %x", GetLastError());
|
||||||
|
free(packet);
|
||||||
|
terminate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
read = recv(m_socket, packet->data, packet->length, 0);
|
||||||
|
if (read == -1)
|
||||||
|
{
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
packet->data[packet->length] = '\0';
|
||||||
|
if (UserConfigParams::m_rich_presence_debug)
|
||||||
|
Log::debug("RichPresence", "<= (OP %d len=%d) %s is data (READ %d bytes)",
|
||||||
|
packet->op, packet->length, packet->data, read);
|
||||||
|
|
||||||
|
free(packet);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RichPresence::finishConnection(RichPresence* self) {
|
||||||
|
#ifndef DISABLE_RPC
|
||||||
|
// We read all the data from the socket. We're clear now to handshake!
|
||||||
|
self->handshake();
|
||||||
|
|
||||||
|
// Make sure we get a response!
|
||||||
|
self->readData();
|
||||||
|
|
||||||
|
// Okay to go nonblocking now!
|
||||||
|
#if !defined(WIN32) && defined(O_NONBLOCK)
|
||||||
|
fcntl(self->m_socket, F_SETFL, O_NONBLOCK);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
self->m_ready = true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RichPresence::tryConnect(std::string path) {
|
||||||
|
#ifndef DISABLE_RPC
|
||||||
|
#if !defined(WIN32) && defined(AF_UNIX)
|
||||||
|
struct sockaddr_un addr = {
|
||||||
|
.sun_family = AF_UNIX
|
||||||
|
};
|
||||||
|
memset(addr.sun_path, 0, sizeof(addr.sun_path));
|
||||||
|
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path.c_str());
|
||||||
|
if(connect(m_socket, (struct sockaddr *) &addr, sizeof(addr)) == -1)
|
||||||
|
{
|
||||||
|
// Something is probably wrong:
|
||||||
|
if (errno != ENOENT && errno != ECONNREFUSED)
|
||||||
|
{
|
||||||
|
perror("Couldn't open Discord socket!");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Connected!
|
||||||
|
m_connected = true;
|
||||||
|
#elif defined(WIN32)
|
||||||
|
// Windows
|
||||||
|
m_socket = CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
|
||||||
|
if (m_socket == INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
DWORD error = GetLastError();
|
||||||
|
if (error != ERROR_FILE_NOT_FOUND)
|
||||||
|
{
|
||||||
|
LPSTR errorText = NULL;
|
||||||
|
FormatMessage(
|
||||||
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||||
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||||
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
NULL,
|
||||||
|
error,
|
||||||
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
|
(LPTSTR)&errorText,
|
||||||
|
0, NULL);
|
||||||
|
|
||||||
|
Log::warn("RichPresence", "Couldn't open file! %s Error: %ls", path.c_str(), errorText);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_connected = true;
|
||||||
|
#endif
|
||||||
|
#endif // DISABLE_RPC
|
||||||
|
return m_connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RichPresence::handshake() {
|
||||||
|
#ifndef DISABLE_RPC
|
||||||
|
if (UserConfigParams::m_rich_presence_debug)
|
||||||
|
Log::debug("RichPresence", "Starting handshake...");
|
||||||
|
HardwareStats::Json json;
|
||||||
|
json.add<int>("v", 1);
|
||||||
|
json.add("client_id", std::string(UserConfigParams::m_discord_client_id));
|
||||||
|
json.finish();
|
||||||
|
sendData(OP_HANDSHAKE, json.toString());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RichPresence::sendData(int32_t op, std::string json) {
|
||||||
|
#ifndef DISABLE_RPC
|
||||||
|
// Handshake will make us ready:
|
||||||
|
if (op != OP_HANDSHAKE && !m_ready)
|
||||||
|
{
|
||||||
|
Log::warn("RichPresence", "Tried sending data while not ready?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (UserConfigParams::m_rich_presence_debug)
|
||||||
|
Log::debug("RichPresence", "=> %s", json.c_str());
|
||||||
|
int32_t size = json.size();
|
||||||
|
size_t length = (sizeof(int32_t) * 2) + (size * sizeof(char));
|
||||||
|
struct discordPacket* packet = (struct discordPacket*) malloc(
|
||||||
|
length
|
||||||
|
);
|
||||||
|
packet->op = op;
|
||||||
|
packet->length = size;
|
||||||
|
// Note we aren't copying the NUL at the end
|
||||||
|
memcpy(&packet->data, json.c_str(), json.size());
|
||||||
|
#if !defined(WIN32) && defined(AF_UNIX)
|
||||||
|
int flags = 0;
|
||||||
|
#ifdef MSG_NOSIGNAL
|
||||||
|
flags |= MSG_NOSIGNAL;
|
||||||
|
#endif
|
||||||
|
if (send(m_socket, packet, length, flags) == -1)
|
||||||
|
{
|
||||||
|
if (errno != EPIPE)
|
||||||
|
perror("Couldn't send data to Discord socket!");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (UserConfigParams::m_rich_presence_debug)
|
||||||
|
Log::debug("RichPresence", "Got an EPIPE, closing");
|
||||||
|
// EPIPE, cleanup!
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#elif defined(WIN32)
|
||||||
|
DWORD written;
|
||||||
|
WriteFile(m_socket, packet, length, &written, NULL);
|
||||||
|
// TODO
|
||||||
|
if(written != length)
|
||||||
|
{
|
||||||
|
if (UserConfigParams::m_rich_presence_debug)
|
||||||
|
Log::debug("RichPresence", "Amount written != data size! Closing");
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
#endif // AF_UNIX
|
||||||
|
free(packet);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RichPresence::update(bool force) {
|
||||||
|
#ifndef DISABLE_RPC
|
||||||
|
if (STKProcess::getType() != PT_MAIN)
|
||||||
|
{
|
||||||
|
// Don't update on server thread
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
time_t now = time(NULL);
|
||||||
|
if ((now - m_last) < 10 && !force)
|
||||||
|
{
|
||||||
|
// Only update every 10s
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check more often if we're not ready
|
||||||
|
if (m_ready || !m_connected)
|
||||||
|
{
|
||||||
|
// Update timer
|
||||||
|
m_last = now;
|
||||||
|
}
|
||||||
|
// Retry connection:
|
||||||
|
if (!m_connected)
|
||||||
|
{
|
||||||
|
doConnect();
|
||||||
|
}
|
||||||
|
if (!m_ready)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (UserConfigParams::m_rich_presence_debug)
|
||||||
|
Log::debug("RichPresence", "Updating status!");
|
||||||
|
|
||||||
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> convert;
|
||||||
|
|
||||||
|
std::string playerName;
|
||||||
|
PlayerProfile *player = PlayerManager::getCurrentPlayer();
|
||||||
|
if (player)
|
||||||
|
{
|
||||||
|
if (PlayerManager::getCurrentOnlineState() == PlayerProfile::OS_GUEST ||
|
||||||
|
PlayerManager::getCurrentOnlineState() == PlayerProfile::OS_SIGNED_IN)
|
||||||
|
{
|
||||||
|
playerName = convert.to_bytes(player->getLastOnlineName().c_str()) + "@stk";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
playerName = convert.to_bytes(player->getName().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
playerName = "Guest";
|
||||||
|
World* world = World::getWorld();
|
||||||
|
RaceManager *raceManager = RaceManager::get();
|
||||||
|
std::string trackId = raceManager->getTrackName();
|
||||||
|
std::string difficulty = convert.to_bytes(raceManager->getDifficultyName(
|
||||||
|
raceManager->getDifficulty()
|
||||||
|
).c_str());
|
||||||
|
std::string minorModeName = convert.to_bytes(raceManager->getNameOf(
|
||||||
|
raceManager->getMinorMode()
|
||||||
|
).c_str());
|
||||||
|
// Discord takes the time when we started as unix timestamp
|
||||||
|
uint64_t since = (now * 1000) - StkTime::getMonoTimeMs();
|
||||||
|
if (world)
|
||||||
|
{
|
||||||
|
since += world->getStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// {cmd:SET_ACTIVITY,args:{activity:{},pid:0},nonce:0}
|
||||||
|
|
||||||
|
HardwareStats::Json base;
|
||||||
|
base.add("cmd", "SET_ACTIVITY");
|
||||||
|
|
||||||
|
HardwareStats::Json args;
|
||||||
|
HardwareStats::Json activity;
|
||||||
|
|
||||||
|
std::string trackName = convert.to_bytes(_("Getting ready to race").c_str());
|
||||||
|
if (world)
|
||||||
|
{
|
||||||
|
Track* track = track_manager->getTrack(trackId);
|
||||||
|
if (track)
|
||||||
|
trackName = convert.to_bytes(track->getName().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto protocol = LobbyProtocol::get<ClientLobby>();
|
||||||
|
if (protocol != nullptr && protocol.get()->getJoinedServer() != nullptr)
|
||||||
|
{
|
||||||
|
trackName.append(" - ");
|
||||||
|
trackName.append(convert.to_bytes(
|
||||||
|
protocol.get()->getJoinedServer().get()->getName().c_str()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.add("state", std::string(trackName.c_str()));
|
||||||
|
if (world)
|
||||||
|
activity.add("details", minorModeName + " (" + difficulty + ")");
|
||||||
|
|
||||||
|
HardwareStats::Json assets;
|
||||||
|
if (world)
|
||||||
|
{
|
||||||
|
Track* track = track_manager->getTrack(trackId);
|
||||||
|
assets.add("large_text", convert.to_bytes(track->getName().c_str()));
|
||||||
|
assets.add("large_image", track->isAddon() ?
|
||||||
|
"addons" : "track_" + trackId);
|
||||||
|
AbstractKart *abstractKart = world->getLocalPlayerKart(0);
|
||||||
|
if (abstractKart)
|
||||||
|
{
|
||||||
|
const KartProperties* kart = abstractKart->getKartProperties();
|
||||||
|
assets.add("small_image", kart->isAddon() ?
|
||||||
|
"addons" : "kart_" + abstractKart->getIdent());
|
||||||
|
std::string kartName = convert.to_bytes(kart->getName().c_str());
|
||||||
|
assets.add("small_text", kartName + " (" + playerName + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assets.add("large_text", "SuperTuxKart");
|
||||||
|
assets.add("large_image", "logo");
|
||||||
|
assets.add("small_text", playerName);
|
||||||
|
// std::string filename = std::string(basename(player->getIconFilename().c_str()));
|
||||||
|
// assets->add("small_image", "kart_" + filename);
|
||||||
|
}
|
||||||
|
assets.finish();
|
||||||
|
activity.add<std::string>("assets", assets.toString());
|
||||||
|
|
||||||
|
HardwareStats::Json timestamps;
|
||||||
|
timestamps.add<std::string>("start", std::to_string(since));
|
||||||
|
|
||||||
|
timestamps.finish();
|
||||||
|
activity.add<std::string>("timestamps", timestamps.toString());
|
||||||
|
|
||||||
|
activity.finish();
|
||||||
|
args.add<std::string>("activity", activity.toString());
|
||||||
|
int pid = 0;
|
||||||
|
#ifdef WIN32
|
||||||
|
pid = _getpid();
|
||||||
|
#elif !defined(DISABLE_RPC)
|
||||||
|
pid = getppid();
|
||||||
|
#endif
|
||||||
|
args.add<int>("pid", pid);
|
||||||
|
args.finish();
|
||||||
|
base.add<int>("nonce", now);
|
||||||
|
base.add<std::string>("args", args.toString());
|
||||||
|
base.finish();
|
||||||
|
|
||||||
|
sendData(OP_DATA, base.toString());
|
||||||
|
#endif // DISABLE_RPC
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
42
src/io/rich_presence.hpp
Normal file
42
src/io/rich_presence.hpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#ifdef WIN32
|
||||||
|
#include <namedpipeapi.h>
|
||||||
|
#endif
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace RichPresenceNS {
|
||||||
|
// There are more, but we don't need to use them
|
||||||
|
enum OPCodes {
|
||||||
|
OP_HANDSHAKE = 0,
|
||||||
|
OP_DATA = 1,
|
||||||
|
};
|
||||||
|
struct discordPacket {
|
||||||
|
int32_t op;
|
||||||
|
int32_t length;
|
||||||
|
char data[];
|
||||||
|
};
|
||||||
|
class RichPresence {
|
||||||
|
private:
|
||||||
|
bool m_connected;
|
||||||
|
bool m_ready;
|
||||||
|
time_t m_last;
|
||||||
|
#ifdef WIN32
|
||||||
|
HANDLE m_socket;
|
||||||
|
#else
|
||||||
|
int m_socket;
|
||||||
|
#endif
|
||||||
|
std::thread* m_thread;
|
||||||
|
bool tryConnect(std::string path);
|
||||||
|
bool doConnect();
|
||||||
|
void terminate();
|
||||||
|
void sendData(int32_t op, std::string json);
|
||||||
|
void handshake();
|
||||||
|
void readData();
|
||||||
|
static void finishConnection(RichPresence* self);
|
||||||
|
public:
|
||||||
|
RichPresence();
|
||||||
|
~RichPresence();
|
||||||
|
void update(bool force);
|
||||||
|
static RichPresence* get();
|
||||||
|
static void destroy();
|
||||||
|
};
|
||||||
|
}
|
@ -290,6 +290,7 @@ extern "C" {
|
|||||||
#include "utils/stk_process.hpp"
|
#include "utils/stk_process.hpp"
|
||||||
#include "utils/string_utils.hpp"
|
#include "utils/string_utils.hpp"
|
||||||
#include "utils/translation.hpp"
|
#include "utils/translation.hpp"
|
||||||
|
#include "io/rich_presence.hpp"
|
||||||
|
|
||||||
static void cleanSuperTuxKart();
|
static void cleanSuperTuxKart();
|
||||||
static void cleanUserConfig();
|
static void cleanUserConfig();
|
||||||
@ -2573,6 +2574,8 @@ int main(int argc, char *argv[])
|
|||||||
cleanSuperTuxKart();
|
cleanSuperTuxKart();
|
||||||
NetworkConfig::destroy();
|
NetworkConfig::destroy();
|
||||||
|
|
||||||
|
RichPresenceNS::RichPresence::destroy();
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
MemoryLeaks::checkForLeaks();
|
MemoryLeaks::checkForLeaks();
|
||||||
#endif
|
#endif
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
#include "utils/string_utils.hpp"
|
#include "utils/string_utils.hpp"
|
||||||
#include "utils/time.hpp"
|
#include "utils/time.hpp"
|
||||||
#include "utils/translation.hpp"
|
#include "utils/translation.hpp"
|
||||||
|
#include "io/rich_presence.hpp"
|
||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@ -714,6 +715,8 @@ void MainLoop::run()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RichPresenceNS::RichPresence::get()->update(false);
|
||||||
|
|
||||||
if (auto gp = GameProtocol::lock())
|
if (auto gp = GameProtocol::lock())
|
||||||
{
|
{
|
||||||
gp->sendActions();
|
gp->sendActions();
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
#include <irrlicht.h>
|
#include <irrlicht.h>
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
WorldStatus::WorldStatus() : m_process_type(STKProcess::getType())
|
WorldStatus::WorldStatus() : m_process_type(STKProcess::getType()), m_started_at(StkTime::getMonoTimeMs())
|
||||||
{
|
{
|
||||||
if (m_process_type == PT_MAIN)
|
if (m_process_type == PT_MAIN)
|
||||||
main_loop->setFrameBeforeLoadingWorld();
|
main_loop->setFrameBeforeLoadingWorld();
|
||||||
@ -72,6 +72,7 @@ WorldStatus::WorldStatus() : m_process_type(STKProcess::getType())
|
|||||||
*/
|
*/
|
||||||
void WorldStatus::reset(bool restart)
|
void WorldStatus::reset(bool restart)
|
||||||
{
|
{
|
||||||
|
m_started_at = StkTime::getMonoTimeMs();
|
||||||
m_time = 0.0f;
|
m_time = 0.0f;
|
||||||
m_time_ticks = 0;
|
m_time_ticks = 0;
|
||||||
m_auxiliary_ticks = 0;
|
m_auxiliary_ticks = 0;
|
||||||
|
@ -111,6 +111,9 @@ private:
|
|||||||
/** The third sound to be played in ready, set, go. */
|
/** The third sound to be played in ready, set, go. */
|
||||||
SFXBase *m_start_sound;
|
SFXBase *m_start_sound;
|
||||||
|
|
||||||
|
/** (Unix) time when we started */
|
||||||
|
uint64_t m_started_at;
|
||||||
|
|
||||||
/** The clock mode: normal counting forwards, or countdown */
|
/** The clock mode: normal counting forwards, or countdown */
|
||||||
ClockType m_clock_mode;
|
ClockType m_clock_mode;
|
||||||
protected:
|
protected:
|
||||||
@ -200,6 +203,10 @@ public:
|
|||||||
/** Returns the current race time. */
|
/** Returns the current race time. */
|
||||||
float getTime() const { return (float)m_time; }
|
float getTime() const { return (float)m_time; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
/** Returns the start time. */
|
||||||
|
uint64_t getStart() const { return m_started_at; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Returns the current race time in time ticks (i.e. based on the physics
|
/** Returns the current race time in time ticks (i.e. based on the physics
|
||||||
* time step size). */
|
* time step size). */
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
#include "utils/stk_process.hpp"
|
#include "utils/stk_process.hpp"
|
||||||
#include "utils/string_utils.hpp"
|
#include "utils/string_utils.hpp"
|
||||||
#include "utils/translation.hpp"
|
#include "utils/translation.hpp"
|
||||||
|
#include "io/rich_presence.hpp"
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -688,6 +689,8 @@ void RaceManager::startNextRace()
|
|||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
appletSetCpuBoostMode(ApmCpuBoostMode_Normal);
|
appletSetCpuBoostMode(ApmCpuBoostMode_Normal);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
RichPresenceNS::RichPresence::get()->update(true);
|
||||||
} // startNextRace
|
} // startNextRace
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------
|
//---------------------------------------------------------------------------------------------
|
||||||
@ -970,6 +973,8 @@ void RaceManager::exitRace(bool delete_world)
|
|||||||
|
|
||||||
m_saved_gp = NULL;
|
m_saved_gp = NULL;
|
||||||
m_track_number = 0;
|
m_track_number = 0;
|
||||||
|
|
||||||
|
RichPresenceNS::RichPresence::get()->update(true);
|
||||||
} // exitRace
|
} // exitRace
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------
|
//---------------------------------------------------------------------------------------------
|
||||||
|
Loading…
x
Reference in New Issue
Block a user