diff --git a/presence/discord.cpp b/presence/discord.cpp new file mode 100644 index 000000000..c4604f8dd --- /dev/null +++ b/presence/discord.cpp @@ -0,0 +1,151 @@ +#include "discord.h" +#include "discord_rpc.h" +#include +#include +#include +#include +#include + +/** + * @brief Constructor for the Discord class. + * @author Kevin Mora (morkev) + * + * Initializes the Discord class with the provided launch code (Discord application ID) + * and sets the application_running_ flag to false. + * + * @param launch_code The Discord application client ID. + */ +Discord::Discord(const std::string &launch_code) + : launch_code_(launch_code), application_running_(false), time_of_start_(0) {} + +/** + * @brief Initializes the Discord RPC connection. + * + * Sets up the event handlers for Discord Rich Presence and connects to Discord + * using the provided launch code. + */ +void Discord::Initialize() { + DiscordEventHandlers handlers{}; + Discord_Initialize(launch_code_.c_str(), &handlers, 1, nullptr); +} + +/** + * @brief Shuts down the Discord RPC connection. + * + * Terminates the connection to Discord and cleans up any active presence. + */ +void Discord::Shutdown() { + Discord_Shutdown(); +} + +/** + * @brief Checks if a process with the given name is running on the system. + * + * This function iterates over all running processes to check if a process + * named `process_name` (e.g., `shadps4.exe`) is running. + * + * @param process_name The name of the process to check for. + * @return true if the process is running, false otherwise. + */ +bool Discord::IsProcessRunning(const std::string &process_name) { + for (const auto &entry : std::filesystem::directory_iterator("/proc")) { + try { + std::string pid = entry.path().filename(); + std::string cmd_path = "/proc/" + pid + "/comm"; + std::ifstream cmd_file(cmd_path); + std::string cmd; + std::getline(cmd_file, cmd); + if (cmd == process_name) { + return true; + } + } catch (...) { + continue; + } + } + return false; +} + +/** + * @brief Retrieves the current presence data for Discord. + * + * Determines the state, large image, and large image text based on whether + * `shadps4.exe` is running. If the process is found, it updates the status + * to indicate the application is running. + * + * @param state Reference to the string where the current state will be stored (e.g., "In ShadPS4"). + * @param large_image Reference to the string where the large image key will be stored. + * @param large_text Reference to the string where the large image's tooltip text will be stored. + */ +void Discord::GetData(std::string &state, std::string &large_image, std::string &large_text) { + if (IsProcessRunning("shadps4.exe")) { + state = "In ShadPS4"; + large_image = "shadps4"; + large_text = "ShadPS4"; + } else { + state = "Unknown"; + large_image = "default"; + large_text = "Unknown"; + } +} + +/** + * @brief Updates the Discord Rich Presence with the latest status. + * + * If the application `shadps4.exe` is running, this method updates the start time + * and sends the current state, large image, and text to Discord. + * If the application is closed, it clears the presence from Discord. + */ +void Discord::UpdatePresence() { + std::string state, large_image, large_text; + GetData(state, large_image, large_text); + + DiscordRichPresence presence{}; + presence.state = state.c_str(); + presence.large_image_key = large_image.c_str(); + presence.large_image_text = large_text.c_str(); + presence.start_timestamp = time_of_start_; + + if (state != "Unknown") { + Discord_UpdatePresence(&presence); + std::cout << "Discord Rich Presence updated.\n"; + } else { + Discord_ClearPresence(); + std::cout << "Application closed. Presence cleared.\n"; + } +} + +/** + * @brief Runs the main loop for updating Discord Rich Presence. + * + * Continuously checks if the `shadps4.exe` process is running and updates the Discord + * status accordingly. This function handles the timing of updates and stops the timer + * when the application is closed. + */ +void Discord::Run() { + Initialize(); + while (true) { + try { + std::string state, large_image, large_text; + GetData(state, large_image, large_text); + + if (state != "Unknown") { + if (!application_running_) { + application_running_ = true; + time_of_start_ = std::time(nullptr); // Set start time when the application starts + } + UpdatePresence(); + std::this_thread::sleep_for(std::chrono::seconds(10)); // Check every 10 seconds + } else { + if (application_running_) { + application_running_ = false; + std::cout << "Application stopped.\n"; + } + std::this_thread::sleep_for(std::chrono::seconds(1)); // Faster check if app is not running + } + } catch (const std::exception &e) { + std::cerr << "Error: " << e.what() << ". Retrying...\n"; + std::this_thread::sleep_for(std::chrono::seconds(5)); + } + } + Shutdown(); +} \ No newline at end of file diff --git a/presence/discord.h b/presence/discord.h new file mode 100644 index 000000000..0fb4f85f7 --- /dev/null +++ b/presence/discord.h @@ -0,0 +1,64 @@ +#ifndef DISCORD_H +#define DISCORD_H + +#include +#include "discord_rpc.h" + +/** + * @class Discord + * @brief Manages Discord Rich Presence for the shadPS4 Emulator. + * @author Kevin Mora (morkev) + * + * The Discord class is responsible for managing Discord Rich Presence updates, + * detecting if the `shadps4.exe` process is running, and updating Discord status accordingly. + * It also handles connecting and disconnecting from the Discord RPC service. + * + * @param launch_code_ Discord application client ID. + * @param application_running_ Tracks if the application (`shadps4.exe`) is running. + * @param time_of_start_ Tracks the time when the application starts for presence timestamping. + */ +class Discord { +private: + std::string launch_code_; + bool application_running_; + time_t time_of_start_; + + /** + * @brief Checks if a specific process is running on the system. + * + * This method searches for a running process by its name. + * + * @param process_name The name of the process to check (e.g., `shadps4.exe`). + * @return true if the process is running, false otherwise. + */ + bool IsProcessRunning(const std::string &process_name); + + /** + * @brief Retrieves the current status and presence data for Discord. + * + * This method sets the status, large image, and text details based on whether + * `shadps4.exe` is currently running. + * + * @param state Output parameter for the current state (e.g., "In ShadPS4"). + * @param large_image Output parameter for the large image to display (e.g., "shadps4"). + * @param large_text Output parameter for the large image's text (e.g., "ShadPS4"). + */ + void GetData(std::string &state, std::string &large_image, std::string &large_text); + +public: + /** + * @brief Constructor for the Discord class. + * + * Initializes the Discord class with the specified launch code for Discord RPC. + * + * @param launch_code Discord application client ID. + */ + explicit Discord(const std::string &launch_code); + + void UpdatePresence(); + void Run(); + void Initialize(); + void Shutdown(); +}; + +#endif \ No newline at end of file diff --git a/presence/discord_rpc.h b/presence/discord_rpc.h new file mode 100644 index 000000000..f8eaafa02 --- /dev/null +++ b/presence/discord_rpc.h @@ -0,0 +1,116 @@ +#ifndef DISCORD_RPC_H +#define DISCORD_RPC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @struct DiscordRichPresence + * @brief Struct representing Discord Rich Presence data. + * @autor Kevin Mora (morkev) + * + * This struct holds various fields that define the Discord Rich Presence status, + * including the state, timestamps, images, party information, and secrets for joining/spectating games. + * + * @param state The main status text for the Rich Presence (e.g., "In ShadPS4"). + * @param details Additional details for the Rich Presence. + * @param start_timestamp The start time for the current activity (Unix timestamp). + * @param end_timestamp The end time for the current activity (Unix timestamp). + * @param large_image_key The key for the large image shown in Discord. + * @param large_image_text The tooltip text for the large image. + * @param small_image_key The key for the small image shown in Discord. + * @param small_image_text The tooltip text for the small image. + * @param party_id Unique identifier for the player's party. + * @param party_size The number of people in the party. + * @param party_max The maximum size of the party. + * @param match_secret Secret for joining a multiplayer match. + * @param join_secret Secret for joining a friend's game. + * @param spectate_secret Secret for spectating a friend's game. + * @param instance Whether or not the game is an instance. +*/ +typedef struct DiscordRichPresence { + const char* state; + const char* details; + int64_t start_timestamp; + int64_t end_timestamp; + const char* large_image_key; + const char* large_image_text; + const char* small_image_key; + const char* small_image_text; + const char* party_id; + int party_size; + int party_max; + const char* match_secret; + const char* join_secret; + const char* spectate_secret; + int8_t instance; +} DiscordRichPresence; + +/** + * @struct DiscordEventHandlers + * @brief Struct representing event handlers for Discord Rich Presence. + * + * This struct defines function pointers for various Discord RPC events, + * such as when the presence is ready, when the connection is disconnected, + * and when there are errors or game join requests. + * + * @param ready Callback function when Discord RPC is ready. + * @param disconnected Callback function when Discord RPC is disconnected. + * @param errored Callback function for handling errors. + * @param join_game Callback function for when a join game request is received. + * @param spectate_game Callback function for when a spectate game request is received. + * @param join_request Callback function for handling a join request. + */ +typedef struct DiscordEventHandlers { + void (*ready)(void); + void (*disconnected)(int errcode, const char* message); + void (*errored)(int errcode, const char* message); + void (*join_game)(const char* join_secret); + void (*spectate_game)(const char* spectate_secret); + void (*join_request)(const char* join_request); +} DiscordEventHandlers; + +/** + * @brief Initializes the Discord Rich Presence. + * + * This function sets up the Discord Rich Presence with the provided application ID, + * event handlers, and optional Steam ID. + * + * @param application_id The Discord application ID. + * @param handlers Pointer to the Discord event handlers. + * @param auto_register Whether or not to auto-register the presence. + * @param optional_steam_id Optional Steam ID for Steam integration. + */ +void Discord_Initialize( + const char* application_id, DiscordEventHandlers* handlers, + int auto_register, const char* optional_steam_id); + +/** + * @brief Shuts down the Discord Rich Presence. + * + * This function disconnects the Rich Presence from Discord and shuts it down. + */ +void Discord_Shutdown(void); + +/** + * @brief Updates the Discord Rich Presence with new information. + * + * This function updates the current Rich Presence data (e.g., state, timestamps, images). + * + * @param presence Pointer to a `DiscordRichPresence` struct containing the new data. + */ +void Discord_UpdatePresence(const DiscordRichPresence* presence); + +/** + * @brief Clears the current Discord Rich Presence. + * + * This function removes any active Rich Presence information from Discord. + */ +void Discord_ClearPresence(void); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file