Mercurial > wgsdk
view src/plugin.c @ 11:e6a594f16403
*: huge refactor
the config file has changed drastically, moving to an ini file from
that custom format; i *would* have used the win32 functions for those,
but they were barely functional, so I decided on using ini.h which is
lightweight enough.
additionally, I've added Deezer support so album art will be displayed!
unfortunately though winhttp is a pain in the ass so if I send a request
with any form of unicode chars in it it just returns a "bad request" error.
I've tried debugging this but I could never really come up with anything:
my hypothesis is that deezer expects their characters in percent-encoded
UTF-8, but winhttp is sending them in some other encoding.
the config dialog was moved out of config.c (overdue) and many more options
are given in the config as well.
main.c has been renamed to plugin.c to better differentiate it from...
everything else.
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Thu, 14 Mar 2024 20:25:37 -0400 |
parents | |
children |
line wrap: on
line source
#include "plugin.h" #include "deezer.h" #include "timer.h" #include "config.h" #include "resource.h" #include "utils.h" #include "dialog/dlg_config.h" /* ugh. */ #ifndef _MSC_VER #define _MSC_VER 1201 #define WGSDK_UGLY_MSC_HACK 1 #endif #include <Winamp/wa_ipc.h> #ifdef WGSDK_UGLY_MSC_HACK #undef _MSC_VER #endif #include "discord_game_sdk.h" #include <assert.h> #define CLIENT_ID (969367220599803955) #define GPPHDR_VER (0x10) #define METADATA_ITEM_SIZE (256) int init(); void conf(); void quit(); struct winamp_gpp g_plugin = { GPPHDR_VER, // version of the plugin, DO NOT change "Discord GameSDK", // name of the plugin init, // function pointer, executed on init event conf, // function pointer, executed on config event quit, // function pointer, executed on quit event NULL, // Winamp main window HWND, loaded by Winamp NULL // HINSTANCE to this DLL, loaded by Winamp }; /* Discord stuff */ struct application { struct IDiscordCore* core; struct IDiscordActivityManager* activities; }; static struct application app = {0}; /* Now we get to our built-in stuff */ struct timer timer_callbacks = {0}; WNDPROC _winamp_proc = NULL; /* ------------------------------------ */ void DISCORD_CALLBACK update_activity_callback(void* data, enum EDiscordResult result) { /* no-op */ } /* a bunch of spaghetti... */ void report_current_song_status(int playback_state) { /* struct to be filled by us */ struct DiscordActivity activity = {0}; assert(playback_state != 0); activity.application_id = CLIENT_ID; strcpy(activity.name, "Winamp"); if (config.show_elapsed_time && playback_state == 1) { /* don't even bother trying to get the elapsed time. */ activity.timestamps.start = get_system_time_in_milliseconds(); /* this is how we can get the end timestamp if we want to: * * LRESULT track_length = SendMessage(g_plugin.hwndParent, WM_WA_IPC, 2, IPC_GETOUTPUTTIME); * activity.timestamps.end = get_system_time_in_milliseconds() + track_length; */ } LPCWSTR filename = (LPCWSTR)SendMessageW(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GET_PLAYING_FILENAME); WCHAR title[METADATA_ITEM_SIZE + 1] = {L'\0'}; extendedFileInfoStructW file_info = {filename, L"title", title, METADATA_ITEM_SIZE}; /* please excuse the spaghetti here, I swear it wasn't this bad before */ int have_metadata = SendMessageW(g_plugin.hwndParent, WM_WA_IPC, (WPARAM)&file_info, IPC_GET_EXTENDED_FILE_INFOW); if (have_metadata) { WCHAR album[METADATA_ITEM_SIZE + 1] = {L'\0'}; WCHAR artist[METADATA_ITEM_SIZE + 1] = {L'\0'}; /* grab artist info */ file_info.metadata = L"artist"; file_info.ret = artist; SendMessageW(g_plugin.hwndParent, WM_WA_IPC, (WPARAM)&file_info, IPC_GET_EXTENDED_FILE_INFOW); if (artist[0] == '\0') { /* fallback to album artist */ file_info.metadata = L"album artist"; SendMessageW(g_plugin.hwndParent, WM_WA_IPC, (WPARAM)&file_info, IPC_GET_EXTENDED_FILE_INFOW); } /* grab album info */ file_info.metadata = L"album"; file_info.ret = album; SendMessageW(g_plugin.hwndParent, WM_WA_IPC, (WPARAM)&file_info, IPC_GET_EXTENDED_FILE_INFOW); /* get thumbnail URL */ if (config.display_song_info && config.display_album_art) { char* image_url = deezer_get_thumbnail(artist, album); strncpy(activity.assets.large_image, image_url ? image_url : "winamp-logo", ARRAYSIZE(activity.assets.large_image) - 1); free(image_url); /* freeing NULL is a no-op */ } /* do NOT use something like ARRAYSIZE here, we need the size of the array *in bytes* */ do { size_t activity_details_offset = 0; if (artist[0] && config.display_song_info && config.display_artist_name) { size_t off = append_wstr_to_utf8(artist, activity.details, activity_details_offset, sizeof(activity.details)); if (!off) break; else activity_details_offset += off; } if (artist[0] && album[0] && config.display_song_info && config.display_artist_name && config.display_album_name) { LPCWSTR delimiter = L" - "; size_t off = append_wstr_to_utf8(delimiter, activity.details, activity_details_offset, sizeof(activity.details)); if (!off) break; else activity_details_offset += off; } if (album[0] && config.display_song_info && config.display_album_name) { size_t off = append_wstr_to_utf8(album, activity.details, activity_details_offset, sizeof(activity.details)); if (!off) break; else activity_details_offset += off; } } while (0); } else { /* fallback to basic info */ wchar_t* winamp_title = (wchar_t*)SendMessageW(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GET_PLAYING_TITLE); wcsncpy(title, winamp_title, METADATA_ITEM_SIZE); } size_t activity_state_offset = 0; do { size_t off = 0; if (title[0] && config.display_song_info && config.display_title) { off = append_wstr_to_utf8(title, activity.state, activity_state_offset, sizeof(activity.state)); if (!off) break; else activity_state_offset += off; LPCWSTR title_delimiter = L" "; off = append_wstr_to_utf8(title_delimiter, activity.state, activity_state_offset, sizeof(activity.state)); if (!off) break; else activity_state_offset += off; } LPCWSTR title_playing = (playback_state == 1) ? L"(Playing)" : L"(Stopped)"; off = append_wstr_to_utf8(title_playing, activity.state, activity_state_offset, sizeof(activity.state)); if (!off) break; else activity_state_offset += off; } while (0); app.activities->update_activity(app.activities, &activity, &app, update_activity_callback); } void report_idle_status(void) { struct DiscordActivity activity = {0}; activity.application_id = CLIENT_ID; strcpy(activity.name, "Winamp"); strcpy(activity.state, "(Idle)"); strcpy(activity.assets.large_image, "winamp-logo"); app.activities->update_activity(app.activities, &activity, &app, update_activity_callback); } void update_rich_presence_details(void) { LONG is_playing = SendMessageW(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_ISPLAYING); switch (is_playing) { case 0: report_idle_status(); break; case 1: case 3: report_current_song_status(is_playing); break; default: break; } } void CALLBACK TimerProc(HWND, UINT, UINT_PTR, DWORD) { app.core->run_callbacks(app.core); } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { if (message == WM_WA_IPC && lParam == IPC_CB_MISC && wParam == IPC_CB_MISC_STATUS) update_rich_presence_details(); return CallWindowProc(_winamp_proc, hwnd, message, wParam, lParam); } int init() { struct DiscordCreateParams params = {0}; DiscordCreateParamsSetDefault(¶ms); params.client_id = CLIENT_ID; params.flags = DiscordCreateFlags_Default; params.event_data = &app; if (DiscordCreate(DISCORD_VERSION, ¶ms, &app.core) != DiscordResult_Ok) return -1; /* don't do this if we don't have discord */ _winamp_proc = (IsWindowUnicode(g_plugin.hwndParent)) ? (WNDPROC)SetWindowLongPtrW(g_plugin.hwndParent, GWLP_WNDPROC, (LONG_PTR)WndProc) : (WNDPROC)SetWindowLongPtrA(g_plugin.hwndParent, GWLP_WNDPROC, (LONG_PTR)WndProc); app.activities = app.core->get_activity_manager(app.core); timer_init(&timer_callbacks, 16, TimerProc); timer_set(&timer_callbacks); cfg_load(&config); update_rich_presence_details(); return 0; } void quit() { assert(!cfg_save(&config)); app.activities->clear_activity(app.activities, &app, update_activity_callback); timer_stop(&timer_callbacks); close_open_http_handles(); } void conf() { DialogBoxW(g_plugin.hDllInstance, (LPWSTR)DIALOG_CONFIG, g_plugin.hwndParent, (DLGPROC)cfg_win_proc); } __declspec(dllexport) struct winamp_gpp* winampGetGeneralPurposePlugin() { return &g_plugin; }