Mercurial > wgsdk
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(¶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 } |