18 Commits

Author SHA1 Message Date
ultrasn0w
f856b65c58 Update README.md 2024-02-29 20:02:35 +01:00
ultrasn0w
991f7f5377 Update README.md 2024-02-29 20:01:47 +01:00
ultrasn0w
011e5f3c97 Update README.md 2024-02-29 20:01:36 +01:00
Justin McKinstry
757b8e5856 Merge jmckinstry's Pull request (#15)
- Look and naming (tried to unify things a bit)
- .gitignore for ignoring files we don't want to track
- Moved APPLICATION_ID to somewhere that could be made safe for Github without making life harder
- Fixed the bug with showing the wrong play state when loading FB2K
- Fixed the bug where tracks with really long titles would cause janky behavior
- Added unencumbered images if folks didn't want to make their own
- Added DEBUG_CONSOLE_PRINTF calls for debugging
- Removed rate limiting that was already handled in Discord's library code
- Removed UnicodeToAnsi as it was unused
2018-10-31 09:39:40 +01:00
ultrasn0w
170dfb3ae6 Whew 2018-09-04 17:26:51 +02:00
ultrasn0w
3c02c9dd95 Update README.md 2018-09-04 17:26:31 +02:00
ultrasn0w
956f6ba8ec Update README.md 2018-09-04 17:26:10 +02:00
ultrasn0w
0bb4f8080c Update README.md 2018-09-04 17:15:58 +02:00
ultrasn0w
579e7cbb7c Merge pull request #13 from darktohka/master
Update Discord RPC to v3.3.0 and fix metadata double click bug
2018-09-04 16:54:23 +02:00
darktohka
fa59966d6c Update Discord RPC to v3.3.0 and fix metadata double click bug 2018-09-04 05:34:38 +03:00
ultrasn0w
da6b7ec8ed Update README.md 2018-02-11 15:58:42 +01:00
ultrasn0w
deb8bbc760 -> and <- 2018-02-10 17:47:51 +01:00
ultrasn0w
211fc232a9 Make the README more fancy! 2018-02-10 17:46:58 +01:00
ultrasn0w
5685d728a6 fancy 2018-02-10 17:43:32 +01:00
ultrasn0w
295b6fa7fa Prepare README.md for 0.3 2018-02-10 17:32:49 +01:00
unknown
169eaa1aec Update Source to 0.3 2018-02-10 17:28:13 +01:00
ultrasn0w
e7025e2967 I found 0 typos 2018-02-07 02:41:06 +01:00
ultrasn0w
10bf05fb29 Clarify 32bit dll usage
https://github.com/ultrasn0w/foo_drpc/issues/2
2017-12-04 17:29:42 +01:00
15 changed files with 284 additions and 246 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
.vs
Debug
Release
foo_drpc/Debug
foo_drpc/Release
foo_drpc/discord-rpc.h
foo_drpc/lib

View File

@@ -1,10 +1,18 @@
# foo_drpc # foo_drpc
Foobar2000 music status for Discord Rich Presence! Foobar2000 music status for Discord Rich Presence!
# Notice
I'm currently no longer actively developing this, but will still merge PRs and reference releases at the [release page](https://github.com/ultrasn0w/foo_drpc/releases).
You mabe also want to check out [this wonderful alternative](https://github.com/TheQwertiest/foo_discord_rich) that was updated much more recently.
# How to use # How to use
1. Grab release, place component .dll in \foobar2000\components\ or drop foo_drpc directory in \%userdir%\AppData\Roaming\foobar2000\user-components\. 1. Grab [release](https://github.com/ultrasn0w/foo_drpc/releases), drop included **foo_drpc** directory in \%userdir%\AppData\Roaming\foobar2000\user-components\ (if you have not moved your AppData somewhere else) or place included .dll Files in \foobar2000\components\.
2. Grab release from https://github.com/discordapp/discord-rpc and place the discord-rpc.dll from the \bin\ directory in your foobar200 root directory (alongside foobar2000.exe). 2. ~~Add foobar2000 to discords detected games (Settings -> Games -> Add it).~~
3. Add foobar2000 to discords detected games (Settings -> Games -> Add it).
![compact view](/foo_drpc1.PNG?raw=true)
![big view 1](/foo_drpc2.PNG?raw=true)
![big view 2](/foo_drpc3.PNG?raw=true)
# How to compile # How to compile
0. Compiled with VS 2017. 0. Compiled with VS 2017.
@@ -12,10 +20,8 @@ Foobar2000 music status for Discord Rich Presence!
2. Drop contents from repository in the prevoiusly created \foo_drpc\. 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\. 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\. 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. 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". 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 # License and Warranty
Check [LICENSE](../master/LICENSE). 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)

View File

@@ -1,222 +1,209 @@
#include <cmath> #include "Plugin.h"
#include "Plugin.h"
// This tells Foobar2000 users what this component does
DECLARE_COMPONENT_VERSION(
DECLARE_COMPONENT_VERSION( "foo_drpc",
"foo_drpc", "0.3",
"0.1", "Foobar2000 music status for Discord Rich Presence! (c) 2018 - ultrasn0w et al");
"<EFBFBD> 2017 - ultrasn0w");
// This tells Foobar2000 what the file really is even if the user renames it (so only one is loaded)
static initquit_factory_t<foo_drpc> foo_interface; VALIDATE_COMPONENT_FILENAME(FOODRPC_NAME".dll");
static std::chrono::time_point<std::chrono::high_resolution_clock> lastT;
static std::chrono::time_point<std::chrono::high_resolution_clock> req; static initquit_factory_t<foo_drpc> foo_interface;
static bool errored; // Still kind of unused
static bool connected; foo_drpc::foo_drpc()
static bool first; {
// This starts at true because
foo_drpc::foo_drpc() // 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
errored = false; connected = true;
connected = true; }
first = true;
} foo_drpc::~foo_drpc()
{
foo_drpc::~foo_drpc() }
{
} void foo_drpc::on_init()
{
void foo_drpc::on_init() static_api_ptr_t<play_callback_manager> pcm;
{
static_api_ptr_t<play_callback_manager> pcm; DEBUG_CONSOLE_PRINTF("Initializing");
pcm->register_callback(
this, pcm->register_callback(
play_callback::flag_on_playback_starting | this,
play_callback::flag_on_playback_stop | play_callback::flag_on_playback_starting |
play_callback::flag_on_playback_pause | play_callback::flag_on_playback_stop |
play_callback::flag_on_playback_new_track | play_callback::flag_on_playback_pause |
play_callback::flag_on_playback_edited | play_callback::flag_on_playback_new_track |
play_callback::flag_on_playback_dynamic_info_track, play_callback::flag_on_playback_edited |
false); play_callback::flag_on_playback_dynamic_info_track,
false);
discordInit();
initDiscordPresence(); discord_init();
} }
void foo_drpc::on_quit() void foo_drpc::discord_init()
{ {
Discord_Shutdown(); memset(&handlers, 0, sizeof(handlers));
static_api_ptr_t<play_callback_manager>()->unregister_callback(this); handlers.ready = callback_discord_connected;
} handlers.disconnected = callback_discord_disconnected;
handlers.errored = callback_discord_errored;
void foo_drpc::on_playback_starting(play_control::t_track_command command, bool pause)
{ Discord_Initialize(APPLICATION_ID, &handlers, 0, NULL);
if (!connected) return;
init_discord_presence();
if (pause) }
{
discordPresence.state = "Paused"; void foo_drpc::init_discord_presence()
discordPresence.smallImageKey = "pause"; {
} memset(&discord_presence, 0, sizeof(discord_presence));
else discord_presence.state = "Initialized";
{ discord_presence.details = "Waiting ...";
switch (command) discord_presence.largeImageKey = "logo";
{ discord_presence.smallImageKey = "stop";
case play_control::track_command_play:
case play_control::track_command_resume: update_discord_presence();
case play_control::track_command_settrack: }
discordPresence.state = "Listening";
discordPresence.smallImageKey = "play"; void foo_drpc::on_quit()
break; {
} DEBUG_CONSOLE_PRINTF("Unloading");
}
Discord_ClearPresence();
metadb_handle_ptr track; Discord_Shutdown();
static_api_ptr_t<playback_control> pbc;
if (pbc->get_now_playing(track)) static_api_ptr_t<play_callback_manager>()->unregister_callback(this);
{ }
on_playback_new_track(track);
} void foo_drpc::on_playback_starting(playback_control::t_track_command command, bool pause)
// updateDiscordPresence(); {
} if (!connected) return;
void foo_drpc::on_playback_stop(play_control::t_stop_reason reason) if (pause)
{ {
if (!connected) return; discord_presence.state = "Paused";
discord_presence.smallImageKey = "pause";
switch (reason) }
{ else
case play_control::stop_reason_user: {
case play_control::stop_reason_eof: switch (command)
case play_control::stop_reason_shutting_down: {
discordPresence.state = "Stopped"; case playback_control::track_command_play:
discordPresence.smallImageKey = "stop"; case playback_control::track_command_next:
break; case playback_control::track_command_prev:
} case playback_control::track_command_resume:
updateDiscordPresence(); case playback_control::track_command_rand:
} case playback_control::track_command_settrack:
discord_presence.state = "Listening";
void foo_drpc::on_playback_pause(bool pause) discord_presence.smallImageKey = "play";
{ break;
if (!connected) return; }
}
discordPresence.state = (pause ? "Paused" : "Listening");
discordPresence.smallImageKey = (pause ? "pause" : "play"); metadb_handle_ptr track;
updateDiscordPresence(); static_api_ptr_t<playback_control> pbc;
} if (pbc->get_now_playing(track))
{
void foo_drpc::on_playback_new_track(metadb_handle_ptr track) on_playback_new_track(track);
{ }
if (!connected) return; }
service_ptr_t<titleformat_object> script; void foo_drpc::on_playback_stop(playback_control::t_stop_reason reason)
pfc::string8 format = "%artist% - %title%"; {
if (!connected) return;
if (static_api_ptr_t<titleformat_compiler>()->compile(script, format))
{ switch (reason)
static_api_ptr_t<playback_control> pbc; {
case playback_control::stop_reason_user:
pbc->playback_format_title_ex( case playback_control::stop_reason_eof:
track, case playback_control::stop_reason_shutting_down:
nullptr, discord_presence.state = "Stopped";
format, discord_presence.smallImageKey = "stop";
script, update_discord_presence();
nullptr, break;
playback_control::display_level_titles); }
}
if (format.get_length() + 1 <= 128) {
static char nya[128]; void foo_drpc::on_playback_pause(bool pause)
size_t destination_size = sizeof(nya); {
strncpy_s(nya, format.get_ptr(), destination_size); if (!connected) return;
nya[destination_size - 1] = '\0';
discord_presence.state = (pause ? "Paused" : "Listening");
discordPresence.state = "Listening"; discord_presence.smallImageKey = (pause ? "pause" : "play");
discordPresence.smallImageKey = "play"; update_discord_presence();
discordPresence.details = nya; }
updateDiscordPresence();
} void foo_drpc::on_playback_new_track(metadb_handle_ptr track)
} {
} if (!connected) return;
void foo_drpc::on_playback_dynamic_info_track(const file_info& info) service_ptr_t<titleformat_object> script;
{ pfc::string8 format = "%artist% - %title%";
metadb_handle_ptr track;
static_api_ptr_t<playback_control> pbc; if (static_api_ptr_t<titleformat_compiler>()->compile(script, format))
if (pbc->get_now_playing(track)) {
{ static_api_ptr_t<playback_control> pbc;
on_playback_new_track(track);
} pbc->playback_format_title_ex(
} track,
nullptr,
void foo_drpc::initDiscordPresence() format,
{ script,
memset(&discordPresence, 0, sizeof(discordPresence)); nullptr,
discordPresence.state = "Initialized"; playback_control::display_level_titles);
discordPresence.details = "topkek";
discordPresence.largeImageKey = "logo"; // If the details size is bigger than MAX_DETAILS_LENGTH chars, truncate it
discordPresence.smallImageKey = "stop"; const size_t MAX_DETAILS_LENGTH = 128;
// discordPresence.partyId = "party1234";
// discordPresence.partySize = 1; size_t details_length = min(format.get_length(), MAX_DETAILS_LENGTH-1); // -1 to give us room for the '\0' in the longest case
// discordPresence.partyMax = 6; static char details[MAX_DETAILS_LENGTH];
updateDiscordPresence(); strncpy_s(details, format.get_ptr(), details_length);
} details[details_length] = '\0';
void foo_drpc::updateDiscordPresence() discord_presence.state = (pbc->is_paused() ? "Paused" : "Listening");
{ discord_presence.smallImageKey = (pbc->is_paused() ? "pause" : "play");
if (first) { discord_presence.details = details;
lastT = std::chrono::high_resolution_clock::now();
first = false; update_discord_presence();
Discord_UpdatePresence(&discordPresence); }
} }
else {
req = std::chrono::high_resolution_clock::now(); void foo_drpc::on_playback_dynamic_info_track(const file_info& info)
std::chrono::duration<double> elapsed = req - lastT; {
// spam protection metadb_handle_ptr track;
if (elapsed.count() > 1.0) { static_api_ptr_t<playback_control> pbc;
Discord_UpdatePresence(&discordPresence); if (pbc->get_now_playing(track))
lastT = std::chrono::high_resolution_clock::now(); {
} on_playback_new_track(track);
} }
} }
void connectedF() void foo_drpc::update_discord_presence()
{ {
connected = true; Discord_UpdatePresence(&discord_presence);
}
#ifdef DISCORD_DISABLE_IO_THREAD
void disconnectedF(int errorCode, const char* message) Discord_UpdateConnection();
{ #endif
connected = false; 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 erroredF(int errorCode, const char* message) }
{
errored = true; void callback_discord_connected(const DiscordUser* request)
} {
foo_interface.get_static_instance().connected = true;
void foo_drpc::discordInit() DEBUG_CONSOLE_PRINTF("Connected to %s.", request->username);
{ }
memset(&handlers, 0, sizeof(handlers));
handlers.ready = connectedF; void callback_discord_disconnected(int errorCode, const char* message)
handlers.disconnected = disconnectedF; {
handlers.errored = erroredF; foo_interface.get_static_instance().connected = false;
// handlers.joinGame = [](const char* joinSecret) {}; DEBUG_CONSOLE_PRINTF("Disconnected (%i): %s.", errorCode, message);
// handlers.spectateGame = [](const char* spectateSecret) {}; }
// handlers.joinRequest = [](const DiscordJoinRequest* request) {};
Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL); void callback_discord_errored(int errorCode, const char* message)
} {
console::printf("*** Error %i: %s.", errorCode, message);
// 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;
}

View File

@@ -2,8 +2,17 @@
#define FOODRPC_PLUGIN_H_ #define FOODRPC_PLUGIN_H_
#include "../../SDK/foobar2000.h" #include "../../SDK/foobar2000.h"
#include "discord-rpc.h" #include "discord-rpc.h"
#include <chrono> #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 : class foo_drpc :
public initquit, public initquit,
@@ -11,32 +20,44 @@ class foo_drpc :
{ {
public: public:
foo_drpc(); foo_drpc();
~foo_drpc(); virtual ~foo_drpc();
DiscordEventHandlers handlers; DiscordEventHandlers handlers;
DiscordRichPresence discordPresence; DiscordRichPresence discord_presence;
// Censored on GitHub :)
const char* APPLICATION_ID = "FILL_IN_HERE";
// 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_init();
void on_quit(); void on_quit();
void discordInit(); // Foobar2000 callback functions
void initDiscordPresence();
void updateDiscordPresence();
LPSTR UnicodeToAnsi(LPCWSTR s);
void on_playback_starting(play_control::t_track_command command, bool paused); 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_stop(play_control::t_stop_reason reason);
void on_playback_pause(bool state); void on_playback_pause(bool state);
void on_playback_new_track(metadb_handle_ptr track); 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_edited(metadb_handle_ptr track) { on_playback_new_track(track); }
void on_playback_dynamic_info_track(const file_info& info); void on_playback_dynamic_info_track(const file_info& info);
// Foobar2000 callback stubs
void on_playback_time(double time) {} void on_playback_time(double time) {}
void on_playback_seek(double time) {} void on_playback_seek(double time) {}
void on_playback_dynamic_info(file_info const& info) {} void on_playback_dynamic_info(file_info const& info) {}
void on_volume_change(float p_new_val) {} 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 #endif

Binary file not shown.

View File

@@ -29,7 +29,7 @@
<UseDebugLibraries>false</UseDebugLibraries> <UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141_xp</PlatformToolset> <PlatformToolset>v141</PlatformToolset>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings"> <ImportGroup Label="ExtensionSettings">
@@ -54,7 +54,7 @@
<WarningLevel>Level3</WarningLevel> <WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization> <Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;FOO_DRPC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;FOO_DRPC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
@@ -72,7 +72,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions> <IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;FOO_DRPC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;FOO_DRPC_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary> <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<RuntimeTypeInfo>false</RuntimeTypeInfo> <RuntimeTypeInfo>false</RuntimeTypeInfo>
<ExceptionHandling>false</ExceptionHandling> <ExceptionHandling>false</ExceptionHandling>
<BufferSecurityCheck>false</BufferSecurityCheck> <BufferSecurityCheck>false</BufferSecurityCheck>

6
foo_drpc/secret.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef FOODRPC_SECRET_H_
#define FOODRPC_SECRET_H_
#define _FOODRPC_SECRED_APPLICATION_ID "FILL_ME_IN"
#endif

BIN
foo_drpc1.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
foo_drpc2.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
foo_drpc3.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -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

BIN
optional_images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
optional_images/pause.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
optional_images/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
optional_images/stop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB