comparison 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
comparison
equal deleted inserted replaced
10:42ac054c0231 11:e6a594f16403
1 #include "plugin.h"
2 #include "deezer.h"
3 #include "timer.h"
4 #include "config.h"
5 #include "resource.h"
6 #include "utils.h"
7 #include "dialog/dlg_config.h"
8
9 /* ugh. */
10 #ifndef _MSC_VER
11 #define _MSC_VER 1201
12 #define WGSDK_UGLY_MSC_HACK 1
13 #endif
14
15 #include <Winamp/wa_ipc.h>
16
17 #ifdef WGSDK_UGLY_MSC_HACK
18 #undef _MSC_VER
19 #endif
20
21 #include "discord_game_sdk.h"
22
23 #include <assert.h>
24
25 #define CLIENT_ID (969367220599803955)
26 #define GPPHDR_VER (0x10)
27 #define METADATA_ITEM_SIZE (256)
28
29 int init();
30 void conf();
31 void quit();
32
33 struct winamp_gpp g_plugin = {
34 GPPHDR_VER, // version of the plugin, DO NOT change
35 "Discord GameSDK", // name of the plugin
36 init, // function pointer, executed on init event
37 conf, // function pointer, executed on config event
38 quit, // function pointer, executed on quit event
39 NULL, // Winamp main window HWND, loaded by Winamp
40 NULL // HINSTANCE to this DLL, loaded by Winamp
41 };
42
43 /* Discord stuff */
44
45 struct application {
46 struct IDiscordCore* core;
47 struct IDiscordActivityManager* activities;
48 };
49
50 static struct application app = {0};
51
52 /* Now we get to our built-in stuff */
53
54 struct timer timer_callbacks = {0};
55
56 WNDPROC _winamp_proc = NULL;
57
58 /* ------------------------------------ */
59
60 void DISCORD_CALLBACK update_activity_callback(void* data, enum EDiscordResult result) {
61 /* no-op */
62 }
63
64 /* a bunch of spaghetti... */
65 void report_current_song_status(int playback_state) {
66 /* struct to be filled by us */
67 struct DiscordActivity activity = {0};
68
69 assert(playback_state != 0);
70
71 activity.application_id = CLIENT_ID;
72 strcpy(activity.name, "Winamp");
73
74 if (config.show_elapsed_time && playback_state == 1) {
75 /* don't even bother trying to get the elapsed time. */
76 activity.timestamps.start = get_system_time_in_milliseconds();
77
78 /* this is how we can get the end timestamp if we want to:
79 *
80 * LRESULT track_length = SendMessage(g_plugin.hwndParent, WM_WA_IPC, 2, IPC_GETOUTPUTTIME);
81 * activity.timestamps.end = get_system_time_in_milliseconds() + track_length;
82 */
83 }
84
85 LPCWSTR filename = (LPCWSTR)SendMessageW(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GET_PLAYING_FILENAME);
86
87 WCHAR title[METADATA_ITEM_SIZE + 1] = {L'\0'};
88 extendedFileInfoStructW file_info = {filename, L"title", title, METADATA_ITEM_SIZE};
89
90 /* please excuse the spaghetti here, I swear it wasn't this bad before */
91 int have_metadata = SendMessageW(g_plugin.hwndParent, WM_WA_IPC, (WPARAM)&file_info, IPC_GET_EXTENDED_FILE_INFOW);
92 if (have_metadata) {
93 WCHAR album[METADATA_ITEM_SIZE + 1] = {L'\0'};
94 WCHAR artist[METADATA_ITEM_SIZE + 1] = {L'\0'};
95
96 /* grab artist info */
97 file_info.metadata = L"artist";
98 file_info.ret = artist;
99 SendMessageW(g_plugin.hwndParent, WM_WA_IPC, (WPARAM)&file_info, IPC_GET_EXTENDED_FILE_INFOW);
100 if (artist[0] == '\0') {
101 /* fallback to album artist */
102 file_info.metadata = L"album artist";
103 SendMessageW(g_plugin.hwndParent, WM_WA_IPC, (WPARAM)&file_info, IPC_GET_EXTENDED_FILE_INFOW);
104 }
105
106 /* grab album info */
107 file_info.metadata = L"album";
108 file_info.ret = album;
109 SendMessageW(g_plugin.hwndParent, WM_WA_IPC, (WPARAM)&file_info, IPC_GET_EXTENDED_FILE_INFOW);
110
111 /* get thumbnail URL */
112 if (config.display_song_info && config.display_album_art) {
113 char* image_url = deezer_get_thumbnail(artist, album);
114 strncpy(activity.assets.large_image, image_url ? image_url : "winamp-logo", ARRAYSIZE(activity.assets.large_image) - 1);
115 free(image_url); /* freeing NULL is a no-op */
116 }
117
118 /* do NOT use something like ARRAYSIZE here, we need the size of the array *in bytes* */
119 do {
120 size_t activity_details_offset = 0;
121
122 if (artist[0] && config.display_song_info && config.display_artist_name) {
123 size_t off = append_wstr_to_utf8(artist, activity.details, activity_details_offset, sizeof(activity.details));
124 if (!off) break;
125 else activity_details_offset += off;
126 }
127
128 if (artist[0] && album[0] && config.display_song_info && config.display_artist_name && config.display_album_name) {
129 LPCWSTR delimiter = L" - ";
130 size_t off = append_wstr_to_utf8(delimiter, activity.details, activity_details_offset, sizeof(activity.details));
131 if (!off) break;
132 else activity_details_offset += off;
133 }
134
135 if (album[0] && config.display_song_info && config.display_album_name) {
136 size_t off = append_wstr_to_utf8(album, activity.details, activity_details_offset, sizeof(activity.details));
137 if (!off) break;
138 else activity_details_offset += off;
139 }
140 } while (0);
141 } else {
142 /* fallback to basic info */
143 wchar_t* winamp_title = (wchar_t*)SendMessageW(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GET_PLAYING_TITLE);
144 wcsncpy(title, winamp_title, METADATA_ITEM_SIZE);
145 }
146
147 size_t activity_state_offset = 0;
148 do {
149 size_t off = 0;
150
151 if (title[0] && config.display_song_info && config.display_title) {
152 off = append_wstr_to_utf8(title, activity.state, activity_state_offset, sizeof(activity.state));
153 if (!off) break;
154 else activity_state_offset += off;
155
156 LPCWSTR title_delimiter = L" ";
157 off = append_wstr_to_utf8(title_delimiter, activity.state, activity_state_offset, sizeof(activity.state));
158 if (!off) break;
159 else activity_state_offset += off;
160 }
161
162 LPCWSTR title_playing = (playback_state == 1) ? L"(Playing)" : L"(Stopped)";
163 off = append_wstr_to_utf8(title_playing, activity.state, activity_state_offset, sizeof(activity.state));
164 if (!off) break;
165 else activity_state_offset += off;
166 } while (0);
167
168 app.activities->update_activity(app.activities, &activity, &app, update_activity_callback);
169 }
170
171 void report_idle_status(void) {
172 struct DiscordActivity activity = {0};
173
174 activity.application_id = CLIENT_ID;
175 strcpy(activity.name, "Winamp");
176 strcpy(activity.state, "(Idle)");
177 strcpy(activity.assets.large_image, "winamp-logo");
178
179 app.activities->update_activity(app.activities, &activity, &app, update_activity_callback);
180 }
181
182 void update_rich_presence_details(void) {
183 LONG is_playing = SendMessageW(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_ISPLAYING);
184
185 switch (is_playing) {
186 case 0:
187 report_idle_status();
188 break;
189 case 1:
190 case 3:
191 report_current_song_status(is_playing);
192 break;
193 default:
194 break;
195 }
196 }
197
198 void CALLBACK TimerProc(HWND, UINT, UINT_PTR, DWORD) {
199 app.core->run_callbacks(app.core);
200 }
201
202 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
203 if (message == WM_WA_IPC && lParam == IPC_CB_MISC && wParam == IPC_CB_MISC_STATUS)
204 update_rich_presence_details();
205
206 return CallWindowProc(_winamp_proc, hwnd, message, wParam, lParam);
207 }
208
209 int init() {
210 struct DiscordCreateParams params = {0};
211
212 DiscordCreateParamsSetDefault(&params);
213 params.client_id = CLIENT_ID;
214 params.flags = DiscordCreateFlags_Default;
215 params.event_data = &app;
216
217 if (DiscordCreate(DISCORD_VERSION, &params, &app.core) != DiscordResult_Ok)
218 return -1;
219
220 /* don't do this if we don't have discord */
221 _winamp_proc = (IsWindowUnicode(g_plugin.hwndParent))
222 ? (WNDPROC)SetWindowLongPtrW(g_plugin.hwndParent, GWLP_WNDPROC, (LONG_PTR)WndProc)
223 : (WNDPROC)SetWindowLongPtrA(g_plugin.hwndParent, GWLP_WNDPROC, (LONG_PTR)WndProc);
224
225 app.activities = app.core->get_activity_manager(app.core);
226
227 timer_init(&timer_callbacks, 16, TimerProc);
228 timer_set(&timer_callbacks);
229
230 cfg_load(&config);
231
232 update_rich_presence_details();
233
234 return 0;
235 }
236
237 void quit() {
238 assert(!cfg_save(&config));
239 app.activities->clear_activity(app.activities, &app, update_activity_callback);
240 timer_stop(&timer_callbacks);
241 close_open_http_handles();
242 }
243
244 void conf() {
245 DialogBoxW(g_plugin.hDllInstance, (LPWSTR)DIALOG_CONFIG, g_plugin.hwndParent, (DLGPROC)cfg_win_proc);
246 }
247
248 __declspec(dllexport) struct winamp_gpp* winampGetGeneralPurposePlugin() {
249 return &g_plugin;
250 }