Mercurial > wgsdk
comparison src/config.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 | 42ac054c0231 |
children |
comparison
equal
deleted
inserted
replaced
10:42ac054c0231 | 11:e6a594f16403 |
---|---|
2 * config.c: | 2 * config.c: |
3 * | 3 * |
4 * Functions to load/save/edit the config. | 4 * Functions to load/save/edit the config. |
5 **/ | 5 **/ |
6 | 6 |
7 /** | |
8 * example config: | |
9 * | |
10 * ----- wgsdk config ---- | |
11 * display_title=1 | |
12 * show_elapsed_time=1 | |
13 **/ | |
14 #include "dirtools.h" | 7 #include "dirtools.h" |
15 #include "config.h" | 8 #include "config.h" |
16 #include "resource.h" | 9 #include "ini.h" |
17 #include "utils.h" | 10 #include "plugin.h" /* g_plugin */ |
18 | 11 |
12 /* bypass intptr_t check */ | |
13 #ifndef _MSC_VER | |
14 #define _MSC_VER 1201 | |
15 #define WGSDK_UGLY_MSC_HACK 1 | |
16 #endif | |
17 | |
18 #include <Winamp/wa_ipc.h> | |
19 | |
20 #ifdef WGSDK_UGLY_MSC_HACK | |
21 #undef _MSC_VER | |
22 #endif | |
23 | |
24 #include <shlwapi.h> | |
19 #include <shlobj.h> | 25 #include <shlobj.h> |
20 #include <shlwapi.h> | |
21 | 26 |
22 #include <assert.h> | 27 #include <assert.h> |
23 #include <stdio.h> | |
24 #include <stdlib.h> | |
25 #include <string.h> | |
26 | 28 |
27 #define MAX_LINE_LENGTH 256 | 29 #define MAX_LINE_LENGTH 256 |
28 | 30 |
29 /* from main */ | 31 #ifndef IPC_GETPLUGINDIRECTORYW |
30 extern void update_rich_presence_details(void); | 32 /* requires Winamp 5.58+ */ |
31 extern struct config config; | 33 #define IPC_GETPLUGINDIRECTORYW 1336 |
34 #endif | |
35 | |
36 /* set defaults */ | |
37 struct config config = { | |
38 .display_title = 1, | |
39 .display_artist_name = 1, | |
40 .display_album_name = 1, | |
41 .display_album_art = 1, | |
42 .display_song_info = 1, | |
43 .show_elapsed_time = 1 | |
44 }; | |
32 | 45 |
33 /* must be free'd by the caller */ | 46 /* must be free'd by the caller */ |
34 LPWSTR cfg_get_path() { | 47 LPWSTR cfg_get_path() { |
35 /* get location of appdata folder */ | 48 LPWSTR plugins_folder = NULL; |
36 LPWSTR appdata_folder; | 49 DWORD winamp_version = SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETVERSION); |
37 SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &appdata_folder); | 50 |
38 | 51 if (winamp_version >= 0x5580) { |
39 /* compatibility with WACUP, falls back to Winamp */ | 52 /* Native wide string version of IPC_GETPLUGINDIRECTORY */ |
40 WCHAR exe_name[MAX_PATH] = {L'\0'}; | 53 LPCWSTR w_plugins_folder = (LPCWSTR)SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW); |
41 if (GetModuleFileNameW(NULL, (LPWSTR)&exe_name, MAX_PATH)) { | 54 size_t len = wcslen(w_plugins_folder); |
42 PathStripPathW(exe_name); | 55 |
43 PathRemoveExtensionW(exe_name); | 56 plugins_folder = calloc(len + 1, sizeof(WCHAR)); |
44 } else { // fail | 57 if (!plugins_folder) |
45 memset(exe_name, '\0', MAX_PATH * sizeof(WCHAR)); | 58 return NULL; |
46 wcscpy(exe_name, L"Winamp"); | 59 |
60 wcsncpy(plugins_folder, w_plugins_folder, len); | |
61 } else if (winamp_version >= 0x5110) { | |
62 /* ANSI string version of IPC_GETPLUGINDIRECTORY, convert */ | |
63 LPCSTR plugins_folder_ansi = (LPCSTR)SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORY); | |
64 int required_size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, plugins_folder_ansi, -1, NULL, 0); | |
65 if (required_size < 0) | |
66 return NULL; | |
67 | |
68 plugins_folder = calloc(required_size, sizeof(WCHAR)); | |
69 if (!plugins_folder) | |
70 return NULL; | |
71 | |
72 int size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, plugins_folder_ansi, -1, plugins_folder, required_size); | |
73 if (size < 0) | |
74 return NULL; | |
75 } else if (winamp_version >= 0x2900) { | |
76 /* Ancient winamp, use IPC_GETINIDIRECTORY and find out from there */ | |
77 LPCSTR ini_directory = (LPCSTR)SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETINIDIRECTORY); | |
78 | |
79 int required_size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ini_directory, -1, NULL, 0); | |
80 if (required_size < 0) | |
81 return NULL; | |
82 | |
83 LPWSTR ini_directory_w = calloc(required_size, sizeof(WCHAR)); | |
84 if (!ini_directory_w) | |
85 return NULL; | |
86 | |
87 int size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ini_directory, -1, ini_directory_w, required_size); | |
88 if (size < 0) { | |
89 free(ini_directory_w); | |
90 return NULL; | |
91 } | |
92 | |
93 plugins_folder = dirtools_concat_paths(ini_directory_w, L"Plugins"); | |
94 free(ini_directory_w); | |
95 } else { | |
96 /* please, please for the love of God update winamp. */ | |
97 WCHAR appdata[MAX_PATH] = {L'\0'}; | |
98 | |
99 HRESULT res = SHGetFolderPathW( | |
100 g_plugin.hwndParent, CSIDL_APPDATA, NULL, | |
101 SHGFP_TYPE_CURRENT, appdata | |
102 ); | |
103 | |
104 if (res != S_OK) | |
105 return NULL; | |
106 | |
107 plugins_folder = dirtools_concat_paths(appdata, L"Winamp\\Plugins"); | |
47 } | 108 } |
48 | 109 |
49 /* concat until we get the final path */ | 110 LPWSTR final = dirtools_concat_paths(plugins_folder, L"wgsdk"); |
50 LPWSTR path = dirtools_concat_paths(appdata_folder, exe_name); | 111 free(plugins_folder); |
51 CoTaskMemFree(appdata_folder); | |
52 | |
53 LPWSTR final = dirtools_concat_paths(path, L"Plugins\\wgsdk"); | |
54 free(path); | |
55 | 112 |
56 return final; | 113 return final; |
114 } | |
115 | |
116 static int cfg_ini_handler(void* data, const char* section, const char* key, const char* value) { | |
117 struct config* config = (struct config*)data; | |
118 | |
119 char* ptr; // used with strtol | |
120 | |
121 if (!strcmp(section, "Display configuration")) { | |
122 if (!strcmp(key, "Display title")) { | |
123 config->display_title = !!strtol(value, &ptr, 10); | |
124 return 1; | |
125 } else if (!strcmp(key, "Display artist name")) { | |
126 config->display_artist_name = !!strtol(value, &ptr, 10); | |
127 return 1; | |
128 } else if (!strcmp(key, "Display album name")) { | |
129 config->display_album_name = !!strtol(value, &ptr, 10); | |
130 return 1; | |
131 } else if (!strcmp(key, "Display album art")) { | |
132 config->display_album_art = !!strtol(value, &ptr, 10); | |
133 return 1; | |
134 } else if (!strcmp(key, "Display song information")) { | |
135 config->display_song_info = !!strtol(value, &ptr, 10); | |
136 return 1; | |
137 } else if (!strcmp(key, "Show elapsed time")) { | |
138 config->show_elapsed_time = !!strtol(value, &ptr, 10); | |
139 return 1; | |
140 } else return 0; | |
141 } else return 0; | |
142 } | |
143 | |
144 char* cfg_ini_reader(char* str, int num, void* stream) { | |
145 HANDLE file = (HANDLE)stream; | |
146 DWORD bytes_read; | |
147 | |
148 return (ReadFile(file, str, num, &bytes_read, NULL) && num == bytes_read) ? str : NULL; | |
57 } | 149 } |
58 | 150 |
59 int cfg_load(struct config* restrict config) { | 151 int cfg_load(struct config* restrict config) { |
60 LPWSTR fold_path = cfg_get_path(); | 152 LPWSTR fold_path = cfg_get_path(); |
61 LPWSTR path = dirtools_concat_paths(fold_path, L"config.txt"); | 153 assert(fold_path); |
154 | |
155 LPWSTR path = dirtools_concat_paths(fold_path, L"config.ini"); | |
62 free(fold_path); | 156 free(fold_path); |
63 | 157 |
64 /* find some real win32 replacement for this */ | 158 HANDLE file = CreateFileW(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
65 FILE* config_fp = _wfopen(path, L"r"); | 159 if (file == INVALID_HANDLE_VALUE) /* file doesn't exist? */ |
66 if (config_fp == NULL) { | 160 return 0; |
67 free(path); | 161 |
162 if (ini_parse_stream(cfg_ini_reader, file, cfg_ini_handler, config)) | |
68 return 1; | 163 return 1; |
69 } | 164 |
70 | 165 CloseHandle(file); |
71 /* parse the config */ | |
72 char line[MAX_LINE_LENGTH] = {0}; | |
73 while (fgets(line, MAX_LINE_LENGTH, config_fp)) { | |
74 /* strtok is okay here. */ | |
75 switch (crc32b((unsigned char*)strtok(line, "="))) { | |
76 case 0x2a666380: // display_title | |
77 config->display_title = !!atoi(strtok(NULL, "=")); | |
78 break; | |
79 case 0xbb631f7f: // show_elapsed_time | |
80 config->show_elapsed_time = !!atoi(strtok(NULL, "=")); | |
81 break; | |
82 default: | |
83 break; | |
84 } | |
85 } | |
86 | 166 |
87 free(path); | 167 free(path); |
88 return 0; | 168 return 0; |
89 } | 169 } |
90 | 170 |
91 int cfg_save(struct config config) { | 171 void cfg_write_int_key(HANDLE file, const char* key, int value) { |
172 DWORD bytes_written; | |
173 | |
174 /* write the key */ | |
175 size_t len = strlen(key) * sizeof(*key); | |
176 WriteFile(file, key, len, &bytes_written, NULL); | |
177 assert(bytes_written == len); | |
178 | |
179 static const char equals[] = {'='}; | |
180 WriteFile(file, equals, sizeof(equals), &bytes_written, NULL); | |
181 assert(bytes_written == sizeof(equals)); | |
182 | |
183 char buf[16] = {'\0'}; | |
184 _itoa(value, buf, 10); | |
185 len = strlen(buf) * sizeof(buf[0]); | |
186 WriteFile(file, (buf), len, &bytes_written, NULL); | |
187 assert(bytes_written == len); | |
188 | |
189 static const char ln[] = {'\r', '\n'}; | |
190 WriteFile(file, ln, sizeof(ln), &bytes_written, NULL); | |
191 assert(bytes_written == sizeof(ln)); | |
192 } | |
193 | |
194 void cfg_write_header(HANDLE file, const char* header) { | |
195 DWORD bytes_written = 0; | |
196 | |
197 static const char l_bracket[] = {'['}; | |
198 WriteFile(file, l_bracket, sizeof(l_bracket), &bytes_written, NULL); | |
199 assert(bytes_written == sizeof(l_bracket)); | |
200 | |
201 const size_t len = strlen(header) * sizeof(*header); | |
202 WriteFile(file, header, len, &bytes_written, NULL); | |
203 assert(bytes_written == len); | |
204 | |
205 static const char r_bracket[] = {']'}; | |
206 WriteFile(file, r_bracket, sizeof(r_bracket), &bytes_written, NULL); | |
207 assert(bytes_written == sizeof(r_bracket)); | |
208 | |
209 static const char ln[] = {'\r', '\n'}; | |
210 WriteFile(file, ln, sizeof(ln), &bytes_written, NULL); | |
211 assert(bytes_written == sizeof(ln)); | |
212 } | |
213 | |
214 int cfg_save(const struct config* config) { | |
92 LPWSTR fold_path = cfg_get_path(); | 215 LPWSTR fold_path = cfg_get_path(); |
216 assert(fold_path); | |
93 assert(!dirtools_create_directory(fold_path)); | 217 assert(!dirtools_create_directory(fold_path)); |
94 | 218 |
95 LPWSTR path = dirtools_concat_paths(fold_path, L"config.txt"); | 219 LPWSTR path = dirtools_concat_paths(fold_path, L"config.ini"); |
96 free(fold_path); | 220 free(fold_path); |
97 | 221 |
98 FILE* config_fp = _wfopen(path, L"w"); | 222 /* dirty little hack that lets me not use goto */ |
99 if (config_fp == NULL) { | 223 do { |
100 free(path); | 224 HANDLE file = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); |
101 return 1; | 225 if (file == INVALID_HANDLE_VALUE) /* huh */ |
102 } | 226 break; |
103 | 227 |
104 fprintf(config_fp, "----- wgsdk config ----\n"); | 228 cfg_write_header(file, "Display configuration"); |
105 fprintf(config_fp, "display_title=%d\n", config.display_title); | 229 |
106 fprintf(config_fp, "show_elapsed_time=%d\n", config.show_elapsed_time); | 230 cfg_write_int_key(file, "Display title", config->display_title); |
107 fclose(config_fp); | 231 cfg_write_int_key(file, "Display artist name", config->display_artist_name); |
232 cfg_write_int_key(file, "Display album name", config->display_album_name); | |
233 cfg_write_int_key(file, "Display album art", config->display_album_art); | |
234 cfg_write_int_key(file, "Display song information", config->display_song_info); | |
235 cfg_write_int_key(file, "Show elapsed time", config->show_elapsed_time); | |
236 | |
237 CloseHandle(file); | |
238 } while (0); | |
239 | |
108 free(path); | 240 free(path); |
109 return 0; | 241 return 0; |
110 } | 242 } |
111 | 243 |
112 /* --------------------------------- */ | 244 #undef WritePrivateProfileIntW |
113 | |
114 #define conf_item_to_dlg(hwnd, cons, var) \ | |
115 { \ | |
116 HWND checkboxHwnd = GetDlgItem(hwnd, cons); \ | |
117 SendMessage(checkboxHwnd, BM_SETCHECK, (var) ? BST_CHECKED : BST_UNCHECKED, 0); \ | |
118 } | |
119 #define dlg_item_to_conf(hwnd, cons, var) \ | |
120 { \ | |
121 HWND checkboxHwnd = GetDlgItem(hwnd, cons); \ | |
122 (var) = (SendMessage(checkboxHwnd, BM_GETCHECK, 0, 0) == BST_CHECKED); \ | |
123 } | |
124 void cfg_get_controls(HWND hWnd) { | |
125 dlg_item_to_conf(hWnd, TITLE_CHECK, config.display_title); | |
126 dlg_item_to_conf(hWnd, ELAPSED_TIME_CHECK, config.show_elapsed_time); | |
127 } | |
128 | |
129 void cfg_set_controls(HWND hWnd) { | |
130 conf_item_to_dlg(hWnd, TITLE_CHECK, config.display_title); | |
131 conf_item_to_dlg(hWnd, ELAPSED_TIME_CHECK, config.show_elapsed_time); | |
132 } | |
133 #undef conf_item_to_dlg | |
134 #undef dlg_item_to_conf | |
135 | |
136 void cfg_on_confirm_settings_dialog(HWND hWnd) { | |
137 cfg_get_controls(hWnd); | |
138 cfg_save(config); | |
139 update_rich_presence_details(); | |
140 } | |
141 | |
142 BOOL CALLBACK cfg_win_proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { | |
143 switch (msg) { | |
144 case WM_INITDIALOG: | |
145 /* do nothing if this is a child window (tab page) callback, pass to the parent */ | |
146 if (GetWindowLong(hWnd, GWL_STYLE) & WS_CHILD) | |
147 return FALSE; | |
148 cfg_set_controls(hWnd); | |
149 return TRUE; | |
150 case WM_COMMAND: | |
151 switch (LOWORD(wParam)) { | |
152 case IDOK: | |
153 cfg_on_confirm_settings_dialog(hWnd); | |
154 EndDialog(hWnd, 0); | |
155 return TRUE; | |
156 case IDCANCEL: | |
157 EndDialog(hWnd, 0); | |
158 return TRUE; | |
159 } | |
160 } | |
161 | |
162 return FALSE; | |
163 } |