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
|
||||
msgid "SuperTuxKart Team"
|
||||
msgstr "SuperTuxKart Team"
|
||||
|
||||
#: rich_presence.cpp:296
|
||||
msgid "Getting ready to race"
|
||||
msgstr "Getting ready to race"
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Modify this file to change the last-modified date when you add/remove a file.
|
||||
# This will then trigger a new cmake run automatically.
|
||||
# This will then trigger a new cmake run automatically.
|
||||
file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp")
|
||||
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
|
||||
file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*")
|
||||
|
@ -42,6 +42,49 @@ namespace HardwareStats
|
||||
m_data ="{";
|
||||
} // 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. */
|
||||
template <typename C>
|
||||
@ -49,7 +92,7 @@ namespace HardwareStats
|
||||
{
|
||||
if(m_data.size()>1) // more than '{'
|
||||
m_data += ",";
|
||||
m_data += "\""+key+"\":"+StringUtils::toString(value);
|
||||
m_data += "\""+sanitize(key)+"\":"+StringUtils::toString(value);
|
||||
} // add
|
||||
// --------------------------------------------------------------------
|
||||
/** Specialisation for adding string values. String values in
|
||||
@ -58,7 +101,7 @@ namespace HardwareStats
|
||||
{
|
||||
if(m_data.size()>1) // more than '{'
|
||||
m_data += ",";
|
||||
m_data += "\""+key+"\":\""+StringUtils::toString(value)+"\"";
|
||||
m_data += "\""+sanitize(key)+"\":\""+StringUtils::toString(sanitize(value))+"\"";
|
||||
} // add
|
||||
// --------------------------------------------------------------------
|
||||
/** Specialisation for adding character pointers. String values in
|
||||
@ -67,7 +110,7 @@ namespace HardwareStats
|
||||
{
|
||||
if(m_data.size()>1) // more than '{'
|
||||
m_data += ",";
|
||||
m_data += "\""+key+"\":\""+StringUtils::toString(s)+"\"";
|
||||
m_data += "\""+sanitize(key)+"\":\""+StringUtils::toString(sanitize(s))+"\"";
|
||||
} // add
|
||||
// --------------------------------------------------------------------
|
||||
void finish()
|
||||
|
@ -1152,6 +1152,14 @@ namespace UserConfigParams
|
||||
PARAM_DEFAULT( StringUserConfigParam("all", "last_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_DEFAULT( StringUserConfigParam("peach", "skin_name",
|
||||
"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/string_utils.hpp"
|
||||
#include "utils/translation.hpp"
|
||||
#include "io/rich_presence.hpp"
|
||||
|
||||
static void cleanSuperTuxKart();
|
||||
static void cleanUserConfig();
|
||||
@ -2573,6 +2574,8 @@ int main(int argc, char *argv[])
|
||||
cleanSuperTuxKart();
|
||||
NetworkConfig::destroy();
|
||||
|
||||
RichPresenceNS::RichPresence::destroy();
|
||||
|
||||
#ifdef DEBUG
|
||||
MemoryLeaks::checkForLeaks();
|
||||
#endif
|
||||
|
@ -56,6 +56,7 @@
|
||||
#include "utils/string_utils.hpp"
|
||||
#include "utils/time.hpp"
|
||||
#include "utils/translation.hpp"
|
||||
#include "io/rich_presence.hpp"
|
||||
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
@ -714,6 +715,8 @@ void MainLoop::run()
|
||||
}
|
||||
}
|
||||
|
||||
RichPresenceNS::RichPresence::get()->update(false);
|
||||
|
||||
if (auto gp = GameProtocol::lock())
|
||||
{
|
||||
gp->sendActions();
|
||||
|
@ -38,7 +38,7 @@
|
||||
#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)
|
||||
main_loop->setFrameBeforeLoadingWorld();
|
||||
@ -72,6 +72,7 @@ WorldStatus::WorldStatus() : m_process_type(STKProcess::getType())
|
||||
*/
|
||||
void WorldStatus::reset(bool restart)
|
||||
{
|
||||
m_started_at = StkTime::getMonoTimeMs();
|
||||
m_time = 0.0f;
|
||||
m_time_ticks = 0;
|
||||
m_auxiliary_ticks = 0;
|
||||
|
@ -111,6 +111,9 @@ private:
|
||||
/** The third sound to be played in ready, set, go. */
|
||||
SFXBase *m_start_sound;
|
||||
|
||||
/** (Unix) time when we started */
|
||||
uint64_t m_started_at;
|
||||
|
||||
/** The clock mode: normal counting forwards, or countdown */
|
||||
ClockType m_clock_mode;
|
||||
protected:
|
||||
@ -200,6 +203,10 @@ public:
|
||||
/** Returns the current race 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
|
||||
* time step size). */
|
||||
|
@ -62,6 +62,7 @@
|
||||
#include "utils/stk_process.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
#include "utils/translation.hpp"
|
||||
#include "io/rich_presence.hpp"
|
||||
|
||||
#ifdef __SWITCH__
|
||||
extern "C" {
|
||||
@ -688,6 +689,8 @@ void RaceManager::startNextRace()
|
||||
#ifdef __SWITCH__
|
||||
appletSetCpuBoostMode(ApmCpuBoostMode_Normal);
|
||||
#endif
|
||||
|
||||
RichPresenceNS::RichPresence::get()->update(true);
|
||||
} // startNextRace
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
@ -970,6 +973,8 @@ void RaceManager::exitRace(bool delete_world)
|
||||
|
||||
m_saved_gp = NULL;
|
||||
m_track_number = 0;
|
||||
|
||||
RichPresenceNS::RichPresence::get()->update(true);
|
||||
} // exitRace
|
||||
|
||||
//---------------------------------------------------------------------------------------------
|
||||
|
Loading…
x
Reference in New Issue
Block a user