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 }