11
|
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(¶ms);
|
|
213 params.client_id = CLIENT_ID;
|
|
214 params.flags = DiscordCreateFlags_Default;
|
|
215 params.event_data = &app;
|
|
216
|
|
217 if (DiscordCreate(DISCORD_VERSION, ¶ms, &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 }
|