diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3784142 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.vs +Debug +Release +foo_drpc/Debug +foo_drpc/Release +foo_drpc/discord-rpc.h +foo_drpc/lib diff --git a/README.md b/README.md index e23a594..57b5ce5 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,8 @@ I'm currently no longer actively developing this, but will still merge PRs and r 2. Drop contents from repository in the prevoiusly created \foo_drpc\. 3. Grab release from https://github.com/discordapp/discord-rpc and place \lib\ with contained discord-rpc.lib in \foo_drpc\. 4. Do the same with \include\discord-rpc.h but this time directly into \foo_drpc\. -5. Get/Create a Discord Application ID which resembles your App at Discords end and fill in to \Plugin.h. -6. Upload 1 large asset for your App with the key "logo", 3 small ones with keys "play", "stop" and "pause". +5. Get/Create a Discord Application ID which resembles your App at Discords end and fill in APPLICATION_ID in \foo_drpc\secret.h. +6. Upload 1 large asset for your App with the key "logo", 3 small ones with keys "play", "stop" and "pause". Use the files in optional_images if you like. # License and Warranty Check [LICENSE](../master/LICENSE). - -Note: Even though I build in a "spam protection" to avoid lots of presence updates being send to the discord servers, I can't guarantee and am not responsible for any actions that may be taken against your account. (Nothing happened during personal testing) diff --git a/foo_drpc/Plugin.cpp b/foo_drpc/Plugin.cpp index 9c621fc..1eddddb 100644 --- a/foo_drpc/Plugin.cpp +++ b/foo_drpc/Plugin.cpp @@ -1,229 +1,209 @@ -#include -#include "Plugin.h" - - -DECLARE_COMPONENT_VERSION( -"foo_drpc", -"0.3", -"Foobar2000 music status for Discord Rich Presence! (c) 2018 - ultrasn0w"); - -static initquit_factory_t foo_interface; -static std::chrono::time_point lastT; -static std::chrono::time_point req; -static bool errored; // Still kind of unused -static bool connected; -static bool first; - -foo_drpc::foo_drpc() -{ - errored = false; - connected = true; - first = true; -} - -foo_drpc::~foo_drpc() -{ -} - -void foo_drpc::on_init() -{ - static_api_ptr_t pcm; - pcm->register_callback( - this, - play_callback::flag_on_playback_starting | - play_callback::flag_on_playback_stop | - play_callback::flag_on_playback_pause | - play_callback::flag_on_playback_new_track | - play_callback::flag_on_playback_edited | - play_callback::flag_on_playback_dynamic_info_track, - false); - discordInit(); - initDiscordPresence(); -} - -void foo_drpc::on_quit() -{ - Discord_ClearPresence(); - Discord_Shutdown(); - static_api_ptr_t()->unregister_callback(this); -} - -void foo_drpc::on_playback_starting(playback_control::t_track_command command, bool pause) -{ - if (!connected) return; - - if (pause) - { - discordPresence.state = "Paused"; - discordPresence.smallImageKey = "pause"; - } - else - { - switch (command) - { - case playback_control::track_command_play: - case playback_control::track_command_next: - case playback_control::track_command_prev: - case playback_control::track_command_resume: - case playback_control::track_command_rand: - case playback_control::track_command_settrack: - discordPresence.state = "Listening"; - discordPresence.smallImageKey = "play"; - break; - } - } - - metadb_handle_ptr track; - static_api_ptr_t pbc; - if (pbc->get_now_playing(track)) - { - on_playback_new_track(track); - } - // updateDiscordPresence(); -} - -void foo_drpc::on_playback_stop(playback_control::t_stop_reason reason) -{ - if (!connected) return; - - switch (reason) - { - case playback_control::stop_reason_user: - case playback_control::stop_reason_eof: - case playback_control::stop_reason_shutting_down: - discordPresence.state = "Stopped"; - discordPresence.smallImageKey = "stop"; - updateDiscordPresence(); - break; - } -} - -void foo_drpc::on_playback_pause(bool pause) -{ - if (!connected) return; - - discordPresence.state = (pause ? "Paused" : "Listening"); - discordPresence.smallImageKey = (pause ? "pause" : "play"); - updateDiscordPresence(); -} - -void foo_drpc::on_playback_new_track(metadb_handle_ptr track) -{ - if (!connected) return; - - service_ptr_t script; - pfc::string8 format = "%artist% - %title%"; - - if (static_api_ptr_t()->compile(script, format)) - { - static_api_ptr_t pbc; - - pbc->playback_format_title_ex( - track, - nullptr, - format, - script, - nullptr, - playback_control::display_level_titles); - - if (format.get_length() + 1 <= 128) { - static char nya[128]; - size_t destination_size = sizeof(nya); - strncpy_s(nya, format.get_ptr(), destination_size); - nya[destination_size - 1] = '\0'; - - discordPresence.state = "Listening"; - discordPresence.smallImageKey = "play"; - discordPresence.details = nya; - updateDiscordPresence(); - } - } -} - -void foo_drpc::on_playback_dynamic_info_track(const file_info& info) -{ - metadb_handle_ptr track; - static_api_ptr_t pbc; - if (pbc->get_now_playing(track)) - { - on_playback_new_track(track); - } -} - -void foo_drpc::initDiscordPresence() -{ - memset(&discordPresence, 0, sizeof(discordPresence)); - discordPresence.state = "Initialized"; - discordPresence.details = "Waiting ..."; - discordPresence.largeImageKey = "logo"; - discordPresence.smallImageKey = "stop"; - // discordPresence.partyId = "party1234"; - // discordPresence.partySize = 1; - // discordPresence.partyMax = 6; - - updateDiscordPresence(); -} - -void foo_drpc::updateDiscordPresence() -{ - if (first) { - lastT = std::chrono::high_resolution_clock::now(); - first = false; - Discord_UpdatePresence(&discordPresence); - } - else { - req = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = req - lastT; - // spam protection - if (elapsed.count() > 0.42) { - Discord_UpdatePresence(&discordPresence); - lastT = std::chrono::high_resolution_clock::now(); - } - } +#include "Plugin.h" + +// This tells Foobar2000 users what this component does +DECLARE_COMPONENT_VERSION( +"foo_drpc", +"0.3", +"Foobar2000 music status for Discord Rich Presence! (c) 2018 - ultrasn0w et al"); + +// This tells Foobar2000 what the file really is even if the user renames it (so only one is loaded) +VALIDATE_COMPONENT_FILENAME(FOODRPC_NAME".dll"); + +static initquit_factory_t foo_interface; + +foo_drpc::foo_drpc() +{ + // This starts at true because + // 1) Discord will not call its connected callback if you start this plugin and it's already running and + // 2) it costs us very little to write updates into the void + connected = true; +} + +foo_drpc::~foo_drpc() +{ +} + +void foo_drpc::on_init() +{ + static_api_ptr_t pcm; + + DEBUG_CONSOLE_PRINTF("Initializing"); + + pcm->register_callback( + this, + play_callback::flag_on_playback_starting | + play_callback::flag_on_playback_stop | + play_callback::flag_on_playback_pause | + play_callback::flag_on_playback_new_track | + play_callback::flag_on_playback_edited | + play_callback::flag_on_playback_dynamic_info_track, + false); + + discord_init(); +} + +void foo_drpc::discord_init() +{ + memset(&handlers, 0, sizeof(handlers)); + handlers.ready = callback_discord_connected; + handlers.disconnected = callback_discord_disconnected; + handlers.errored = callback_discord_errored; + + Discord_Initialize(APPLICATION_ID, &handlers, 0, NULL); + + init_discord_presence(); +} + +void foo_drpc::init_discord_presence() +{ + memset(&discord_presence, 0, sizeof(discord_presence)); + discord_presence.state = "Initialized"; + discord_presence.details = "Waiting ..."; + discord_presence.largeImageKey = "logo"; + discord_presence.smallImageKey = "stop"; + + update_discord_presence(); +} + +void foo_drpc::on_quit() +{ + DEBUG_CONSOLE_PRINTF("Unloading"); + + Discord_ClearPresence(); + Discord_Shutdown(); + + static_api_ptr_t()->unregister_callback(this); +} + +void foo_drpc::on_playback_starting(playback_control::t_track_command command, bool pause) +{ + if (!connected) return; + + if (pause) + { + discord_presence.state = "Paused"; + discord_presence.smallImageKey = "pause"; + } + else + { + switch (command) + { + case playback_control::track_command_play: + case playback_control::track_command_next: + case playback_control::track_command_prev: + case playback_control::track_command_resume: + case playback_control::track_command_rand: + case playback_control::track_command_settrack: + discord_presence.state = "Listening"; + discord_presence.smallImageKey = "play"; + break; + } + } + + metadb_handle_ptr track; + static_api_ptr_t pbc; + if (pbc->get_now_playing(track)) + { + on_playback_new_track(track); + } +} + +void foo_drpc::on_playback_stop(playback_control::t_stop_reason reason) +{ + if (!connected) return; + + switch (reason) + { + case playback_control::stop_reason_user: + case playback_control::stop_reason_eof: + case playback_control::stop_reason_shutting_down: + discord_presence.state = "Stopped"; + discord_presence.smallImageKey = "stop"; + update_discord_presence(); + break; + } +} + +void foo_drpc::on_playback_pause(bool pause) +{ + if (!connected) return; + + discord_presence.state = (pause ? "Paused" : "Listening"); + discord_presence.smallImageKey = (pause ? "pause" : "play"); + update_discord_presence(); +} + +void foo_drpc::on_playback_new_track(metadb_handle_ptr track) +{ + if (!connected) return; + + service_ptr_t script; + pfc::string8 format = "%artist% - %title%"; + + if (static_api_ptr_t()->compile(script, format)) + { + static_api_ptr_t pbc; + + pbc->playback_format_title_ex( + track, + nullptr, + format, + script, + nullptr, + playback_control::display_level_titles); + + // If the details size is bigger than MAX_DETAILS_LENGTH chars, truncate it + const size_t MAX_DETAILS_LENGTH = 128; + + size_t details_length = min(format.get_length(), MAX_DETAILS_LENGTH-1); // -1 to give us room for the '\0' in the longest case + static char details[MAX_DETAILS_LENGTH]; + + strncpy_s(details, format.get_ptr(), details_length); + details[details_length] = '\0'; + + discord_presence.state = (pbc->is_paused() ? "Paused" : "Listening"); + discord_presence.smallImageKey = (pbc->is_paused() ? "pause" : "play"); + discord_presence.details = details; + + update_discord_presence(); + } +} + +void foo_drpc::on_playback_dynamic_info_track(const file_info& info) +{ + metadb_handle_ptr track; + static_api_ptr_t pbc; + if (pbc->get_now_playing(track)) + { + on_playback_new_track(track); + } +} + +void foo_drpc::update_discord_presence() +{ + Discord_UpdatePresence(&discord_presence); + #ifdef DISCORD_DISABLE_IO_THREAD Discord_UpdateConnection(); #endif - Discord_RunCallbacks(); -} - -void connectedF(const DiscordUser* request) -{ - connected = true; -} - -void disconnectedF(int errorCode, const char* message) -{ - connected = false; -} - -void erroredF(int errorCode, const char* message) -{ - errored = true; -} - -void foo_drpc::discordInit() -{ - memset(&handlers, 0, sizeof(handlers)); - handlers.ready = connectedF; - handlers.disconnected = disconnectedF; - handlers.errored = erroredF; - // handlers.joinGame = [](const char* joinSecret) {}; - // handlers.spectateGame = [](const char* spectateSecret) {}; - // handlers.joinRequest = [](const DiscordJoinRequest* request) {}; - Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL); -} - -// thx SuperKoko (unused) -LPSTR foo_drpc::UnicodeToAnsi(LPCWSTR s) -{ - if (s == NULL) return NULL; - int cw = lstrlenW(s); - if (cw == 0) { CHAR *psz = new CHAR[1]; *psz = '\0'; return psz; } - int cc = WideCharToMultiByte(CP_UTF8, 0, s, cw, NULL, 0, NULL, NULL); - if (cc == 0) return NULL; - CHAR *psz = new CHAR[cc + 1]; - cc = WideCharToMultiByte(CP_UTF8, 0, s, cw, psz, cc, NULL, NULL); - if (cc == 0) { delete[] psz; return NULL; } - psz[cc] = '\0'; - return psz; -} + Discord_RunCallbacks(); + + DEBUG_CONSOLE_PRINTF("Ran Discord presence update: %s, %s, %s, %s", discord_presence.state, discord_presence.details, discord_presence.largeImageKey, discord_presence.smallImageKey); +} + +void callback_discord_connected(const DiscordUser* request) +{ + foo_interface.get_static_instance().connected = true; + DEBUG_CONSOLE_PRINTF("Connected to %s.", request->username); +} + +void callback_discord_disconnected(int errorCode, const char* message) +{ + foo_interface.get_static_instance().connected = false; + DEBUG_CONSOLE_PRINTF("Disconnected (%i): %s.", errorCode, message); +} + +void callback_discord_errored(int errorCode, const char* message) +{ + console::printf("*** Error %i: %s.", errorCode, message); +} diff --git a/foo_drpc/Plugin.h b/foo_drpc/Plugin.h index 632dea0..c7a50b2 100644 --- a/foo_drpc/Plugin.h +++ b/foo_drpc/Plugin.h @@ -2,8 +2,17 @@ #define FOODRPC_PLUGIN_H_ #include "../../SDK/foobar2000.h" -#include "discord-rpc.h" -#include +#include "discord-rpc.h" +#include "secret.h" + +#define FOODRPC_NAME "foo_drpc" + +#if defined(_DEBUG) || defined(_FOODRPC_WITH_CONSOLE_LOGGING) + #define FOODRPC_CONSOLE_HEADER FOODRPC_NAME ": " + #define DEBUG_CONSOLE_PRINTF(...) console::printf(FOODRPC_CONSOLE_HEADER __VA_ARGS__) +#else + #define DEBUG_CONSOLE_PRINTF(...) {} +#endif class foo_drpc : public initquit, @@ -11,32 +20,44 @@ class foo_drpc : { public: foo_drpc(); - ~foo_drpc(); + virtual ~foo_drpc(); DiscordEventHandlers handlers; - DiscordRichPresence discordPresence; - // Censored on GitHub :) - const char* APPLICATION_ID = "FILL_IN_HERE"; + DiscordRichPresence discord_presence; + // Otherwise known as CLIENT_ID by Discord documentation + // SET THIS IN "secret.h" + const char* APPLICATION_ID = _FOODRPC_SECRED_APPLICATION_ID; + + bool connected; // If Discord integrator is connected to a running Discord instance + + // Foobar2000 component setup and teardown void on_init(); void on_quit(); - - void discordInit(); - void initDiscordPresence(); - void updateDiscordPresence(); - - LPSTR UnicodeToAnsi(LPCWSTR s); - + + // Foobar2000 callback functions void on_playback_starting(play_control::t_track_command command, bool paused); void on_playback_stop(play_control::t_stop_reason reason); void on_playback_pause(bool state); void on_playback_new_track(metadb_handle_ptr track); void on_playback_edited(metadb_handle_ptr track) { on_playback_new_track(track); } void on_playback_dynamic_info_track(const file_info& info); + + // Foobar2000 callback stubs void on_playback_time(double time) {} void on_playback_seek(double time) {} void on_playback_dynamic_info(file_info const& info) {} void on_volume_change(float p_new_val) {} -}; + + // Discord integration helpers + void discord_init(); + void init_discord_presence(); + void update_discord_presence(); +}; + +// Discord callback functions for notifications of changes +void callback_discord_connected(const DiscordUser* request); +void callback_discord_disconnected(int errorCode, const char* message); +void callback_discord_errored(int errorCode, const char* message); #endif diff --git a/foo_drpc/foo_drpc.vcxproj b/foo_drpc/foo_drpc.vcxproj index ae2a5d2..609df9d 100644 --- a/foo_drpc/foo_drpc.vcxproj +++ b/foo_drpc/foo_drpc.vcxproj @@ -54,7 +54,7 @@ Level3 Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;FOO_DRPC_EXPORTS;%(PreprocessorDefinitions) - MultiThreadedDebug + MultiThreadedDebugDLL Windows @@ -72,7 +72,7 @@ true true WIN32;NDEBUG;_WINDOWS;_USRDLL;FOO_DRPC_EXPORTS;%(PreprocessorDefinitions) - MultiThreaded + MultiThreadedDLL false false false diff --git a/foo_drpc/secret.h b/foo_drpc/secret.h new file mode 100644 index 0000000..be84068 --- /dev/null +++ b/foo_drpc/secret.h @@ -0,0 +1,6 @@ +#ifndef FOODRPC_SECRET_H_ +#define FOODRPC_SECRET_H_ + +#define _FOODRPC_SECRED_APPLICATION_ID "FILL_ME_IN" + +#endif diff --git a/optional_images/Sources.txt b/optional_images/Sources.txt new file mode 100644 index 0000000..c402d13 --- /dev/null +++ b/optional_images/Sources.txt @@ -0,0 +1,11 @@ +logo.png: + Original: https://en.wikipedia.org/wiki/File:Foobar2000_logo_2014.png + Author: Florian Trendelenburg + License: Public Domain + Changes: Resized, borders changed + +play.png, pause.png, stop.png: + Original: http://pluspng.com/png-24756.html + Author: Unlisted + License: Attribution + Changes: Split apart, resized, borders changed diff --git a/optional_images/logo.png b/optional_images/logo.png new file mode 100644 index 0000000..21ae5f4 Binary files /dev/null and b/optional_images/logo.png differ diff --git a/optional_images/pause.png b/optional_images/pause.png new file mode 100644 index 0000000..9c560ba Binary files /dev/null and b/optional_images/pause.png differ diff --git a/optional_images/play.png b/optional_images/play.png new file mode 100644 index 0000000..1250822 Binary files /dev/null and b/optional_images/play.png differ diff --git a/optional_images/stop.png b/optional_images/stop.png new file mode 100644 index 0000000..59c25b7 Binary files /dev/null and b/optional_images/stop.png differ