vgmstream/winamp/in_config.c
2024-08-18 17:50:55 +02:00

490 lines
18 KiB
C

/**
* config for Winamp
*/
#include "in_vgmstream.h"
/* ************************************* */
/* IN_CONFIG */
/* ************************************* */
/* config */
#define CONFIG_APP_NAME TEXT("vgmstream plugin")
#define CONFIG_INI_NAME TEXT("plugin.ini")
#define INI_FADE_TIME TEXT("fade_seconds")
#define INI_FADE_DELAY TEXT("fade_delay")
#define INI_LOOP_COUNT TEXT("loop_count")
#define INI_THREAD_PRIORITY TEXT("thread_priority")
#define INI_LOOP_FOREVER TEXT("loop_forever")
#define INI_IGNORE_LOOP TEXT("ignore_loop")
#define INI_DISABLE_SUBSONGS TEXT("disable_subsongs")
#define INI_DOWNMIX_CHANNELS TEXT("downmix_channels")
#define INI_TAGFILE_DISABLE TEXT("tagfile_disable")
#define INI_FORCE_TITLE TEXT("force_title")
#define INI_EXTS_UNKNOWN_ON TEXT("exts_unknown_on")
#define INI_EXTS_COMMON_ON TEXT("exts_common_on")
#define INI_GAIN_TYPE TEXT("gain_type")
#define INI_CLIP_TYPE TEXT("clip_type")
TCHAR *dlg_priority_strings[7] = {
TEXT("Idle"),
TEXT("Lowest"),
TEXT("Below Normal"),
TEXT("Normal"),
TEXT("Above Normal"),
TEXT("Highest (not recommended)"),
TEXT("Time Critical (not recommended)")
};
TCHAR *dlg_replaygain_strings[] = {
TEXT("None"),
TEXT("Album"),
TEXT("Peak")
};
const int priority_values[7] = {
THREAD_PRIORITY_IDLE,
THREAD_PRIORITY_LOWEST,
THREAD_PRIORITY_BELOW_NORMAL,
THREAD_PRIORITY_NORMAL,
THREAD_PRIORITY_ABOVE_NORMAL,
THREAD_PRIORITY_HIGHEST,
THREAD_PRIORITY_TIME_CRITICAL
};
// todo finish UNICODE (requires IPC_GETINIDIRECTORYW from later SDKs to read the ini path properly)
/* Winamp INI reader */
static void ini_get_filename(In_Module* input_module, TCHAR *inifile) {
if (IsWindow(input_module->hMainWindow) && SendMessage(input_module->hMainWindow, WM_WA_IPC,0,IPC_GETVERSION) >= 0x5000) {
/* newer Winamp with per-user settings */
TCHAR *ini_dir = (TCHAR *)SendMessage(input_module->hMainWindow, WM_WA_IPC, 0, IPC_GETINIDIRECTORY);
cfg_strncpy(inifile, ini_dir, WINAMP_PATH_LIMIT);
cfg_strncat(inifile, TEXT("\\Plugins\\"), WINAMP_PATH_LIMIT);
/* can't be certain that \Plugins already exists in the user dir */
CreateDirectory(inifile,NULL);
cfg_strncat(inifile, CONFIG_INI_NAME, WINAMP_PATH_LIMIT);
}
else {
/* older winamp with single settings */
TCHAR *lastSlash;
GetModuleFileName(NULL, inifile, WINAMP_PATH_LIMIT);
lastSlash = cfg_strrchr(inifile, TEXT('\\'));
*(lastSlash + 1) = 0;
/* XMPlay doesn't have a "plugins" subfolder */
if (settings.is_xmplay)
cfg_strncat(inifile, CONFIG_INI_NAME, WINAMP_PATH_LIMIT);
else
cfg_strncat(inifile, TEXT("Plugins\\") CONFIG_INI_NAME, WINAMP_PATH_LIMIT);
/* Maybe should query IPC_GETINIDIRECTORY and use that, not sure what ancient Winamps need.
* There must be some proper way to handle dirs since other Winamp plugins save config in
* XMPlay correctly (this feels like archaeology, try later) */
}
}
static void ini_get_d(const char *inifile, const char *entry, double defval, double *p_val) {
TCHAR buf[256];
TCHAR defbuf[256];
int consumed, res;
cfg_sprintf(defbuf, TEXT("%.2lf"), defval);
GetPrivateProfileString(CONFIG_APP_NAME, entry, defbuf, buf, 256, inifile);
res = cfg_sscanf(buf, TEXT("%lf%n"), p_val, &consumed);
if (res < 1 || consumed != cfg_strlen(buf) || *p_val < 0) {
*p_val = defval;
}
}
static void ini_get_i(const char *inifile, const char *entry, int defval, int *p_val, int min, int max) {
*p_val = GetPrivateProfileInt(CONFIG_APP_NAME, entry, defval, inifile);
if (*p_val < min || *p_val > max) {
*p_val = defval;
}
}
static void ini_get_b(const char *inifile, const char *entry, int defval, int *p_val) {
ini_get_i(inifile, entry, defval, p_val, 0, 1);
}
static void ini_set_d(const char *inifile, const char *entry, double val) {
TCHAR buf[256];
cfg_sprintf(buf, TEXT("%.2lf"), val);
WritePrivateProfileString(CONFIG_APP_NAME, entry, buf, inifile);
}
static void ini_set_i(const char *inifile, const char *entry, int val) {
TCHAR buf[32];
cfg_sprintf(buf, TEXT("%d"), val);
WritePrivateProfileString(CONFIG_APP_NAME, entry, buf, inifile);
}
static void ini_set_b(const char *inifile, const char *entry, int val) {
ini_set_i(inifile, entry, val);
}
/*static*/ void load_defaults(winamp_settings_t* defaults) {
defaults->thread_priority = 3;
defaults->fade_time = 10.0;
defaults->fade_delay = 0.0;
defaults->loop_count = 2.0;
defaults->loop_forever = 0;
defaults->ignore_loop = 0;
defaults->disable_subsongs = 0;
defaults->downmix_channels = 0;
defaults->tagfile_disable = 0;
defaults->force_title = 0;
defaults->exts_unknown_on = 0;
defaults->exts_common_on = 0;
defaults->gain_type = 1;
defaults->clip_type = 2;
}
/*static*/ void load_config(In_Module* input_module, winamp_settings_t* settings, winamp_settings_t* defaults) {
TCHAR inifile[WINAMP_PATH_LIMIT];
ini_get_filename(input_module, inifile);
ini_get_i(inifile, INI_THREAD_PRIORITY, defaults->thread_priority, &settings->thread_priority, 0, 6);
ini_get_d(inifile, INI_FADE_TIME, defaults->fade_time, &settings->fade_time);
ini_get_d(inifile, INI_FADE_DELAY, defaults->fade_delay, &settings->fade_delay);
ini_get_d(inifile, INI_LOOP_COUNT, defaults->loop_count, &settings->loop_count);
ini_get_b(inifile, INI_LOOP_FOREVER, defaults->loop_forever, &settings->loop_forever);
ini_get_b(inifile, INI_IGNORE_LOOP, defaults->ignore_loop, &settings->ignore_loop);
ini_get_b(inifile, INI_DISABLE_SUBSONGS, defaults->disable_subsongs, &settings->disable_subsongs);
ini_get_i(inifile, INI_DOWNMIX_CHANNELS, defaults->downmix_channels, &settings->downmix_channels, 0, 64);
ini_get_b(inifile, INI_TAGFILE_DISABLE, defaults->tagfile_disable, &settings->tagfile_disable);
ini_get_b(inifile, INI_FORCE_TITLE, defaults->force_title, &settings->force_title);
ini_get_b(inifile, INI_EXTS_UNKNOWN_ON, defaults->exts_unknown_on, &settings->exts_unknown_on);
ini_get_b(inifile, INI_EXTS_COMMON_ON, defaults->exts_common_on, &settings->exts_common_on);
ini_get_i(inifile, INI_GAIN_TYPE, defaults->gain_type, (int*)&settings->gain_type, 0, 3);
ini_get_i(inifile, INI_CLIP_TYPE, defaults->clip_type, (int*)&settings->clip_type, 0, 3);
if (settings->loop_forever && settings->ignore_loop)
settings->ignore_loop = 0;
/* exact 0 was allowed before (AKA "intro only") but confuses people and may result in unplayable files */
if (settings->loop_count <= 0)
settings->loop_count = 1;
}
static void save_config(In_Module* input_module, winamp_settings_t* settings) {
TCHAR inifile[WINAMP_PATH_LIMIT];
ini_get_filename(input_module, inifile);
ini_set_i(inifile, INI_THREAD_PRIORITY, settings->thread_priority);
ini_set_d(inifile, INI_FADE_TIME, settings->fade_time);
ini_set_d(inifile, INI_FADE_DELAY, settings->fade_delay);
ini_set_d(inifile, INI_LOOP_COUNT, settings->loop_count);
ini_set_b(inifile, INI_LOOP_FOREVER, settings->loop_forever);
ini_set_b(inifile, INI_IGNORE_LOOP, settings->ignore_loop);
ini_set_b(inifile, INI_DISABLE_SUBSONGS, settings->disable_subsongs);
ini_set_i(inifile, INI_DOWNMIX_CHANNELS, settings->downmix_channels);
ini_set_b(inifile, INI_TAGFILE_DISABLE, settings->tagfile_disable);
ini_set_b(inifile, INI_FORCE_TITLE, settings->force_title);
ini_set_b(inifile, INI_EXTS_UNKNOWN_ON, settings->exts_unknown_on);
ini_set_b(inifile, INI_EXTS_COMMON_ON, settings->exts_common_on);
ini_set_i(inifile, INI_GAIN_TYPE, settings->gain_type);
ini_set_i(inifile, INI_CLIP_TYPE, settings->clip_type);
}
static void dlg_input_set_d(HWND hDlg, int idc, double val) {
TCHAR buf[256];
cfg_sprintf(buf, TEXT("%.2lf"), val);
SetDlgItemText(hDlg, idc, buf);
}
static void dlg_input_set_i(HWND hDlg, int idc, int val) {
TCHAR buf[32];
cfg_sprintf(buf, TEXT("%d"), val);
SetDlgItemText(hDlg, idc, buf);
}
static void dlg_check_set(HWND hDlg, int idc, int val) {
CheckDlgButton(hDlg, idc, val ? BST_CHECKED : BST_UNCHECKED);
}
static void cfg_combo_set(HWND hDlg, int idc, int val, TCHAR **list, int list_size) {
int i;
HANDLE hCombo = GetDlgItem(hDlg, idc);
for (i = 0; i < list_size; i++) {
SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)list[i]);
}
SendMessage(hCombo, CB_SETCURSEL, val, 0);
}
static void cfg_slider_set(HWND hDlg, int idc1, int idc2, int val, int min, int max, TCHAR **list) {
HANDLE hSlider = GetDlgItem(hDlg, idc1);
SendMessage(hSlider, TBM_SETRANGE, (WPARAM)TRUE, (LPARAM)MAKELONG(min, max));
SendMessage(hSlider, TBM_SETPOS, (WPARAM)TRUE, (LPARAM)val+1);
SetDlgItemText(hDlg, idc2, list[val]);
}
static void dlg_input_get_d(HWND hDlg, int idc, double *p_val, LPCTSTR error, int *p_err) {
TCHAR buf[256];
int res, consumed;
double defval = *p_val;
GetDlgItemText(hDlg, idc, buf, 256);
res = cfg_sscanf(buf, TEXT("%lf%n"), p_val, &consumed);
if (res < 1 || consumed != cfg_strlen(buf) || *p_val < 0) {
MessageBox(hDlg, error, NULL, MB_OK|MB_ICONERROR);
*p_val = defval;
*p_err = 1;
}
}
static void dlg_input_get_i(HWND hDlg, int idc, int *p_val, LPCTSTR error, int *p_err) {
TCHAR buf[32];
int res, consumed;
int defval = *p_val;
GetDlgItemText(hDlg, idc, buf, 32);
res = cfg_sscanf(buf, TEXT("%d%n"), p_val, &consumed);
if (res < 1 || consumed != cfg_strlen(buf) || *p_val < 0) {
MessageBox(hDlg, error, NULL, MB_OK|MB_ICONERROR);
*p_val = defval;
*p_err = 1;
}
}
static void dlg_check_get(HWND hDlg, int idc, int *p_val) {
*p_val = (IsDlgButtonChecked(hDlg, idc) == BST_CHECKED);
}
static void dlg_combo_get(HWND hDlg, int idc, int *p_val) {
*p_val = SendMessage(GetDlgItem(hDlg, idc), CB_GETCURSEL, 0, 0);
}
static int dlg_load_form(HWND hDlg, winamp_settings_t* settings) {
int err = 0;
dlg_input_get_d(hDlg, IDC_FADE_TIME, &settings->fade_time, TEXT("Fade Length must be a positive number"), &err);
dlg_input_get_d(hDlg, IDC_FADE_DELAY, &settings->fade_delay, TEXT("Fade Delay must be a positive number"), &err);
dlg_input_get_d(hDlg, IDC_LOOP_COUNT, &settings->loop_count, TEXT("Loop Count must be a positive number"), &err);
dlg_check_get(hDlg, IDC_LOOP_FOREVER, &settings->loop_forever);
dlg_check_get(hDlg, IDC_IGNORE_LOOP, &settings->ignore_loop);
dlg_check_get(hDlg, IDC_DISABLE_SUBSONGS, &settings->disable_subsongs);
dlg_input_get_i(hDlg, IDC_DOWNMIX_CHANNELS, &settings->downmix_channels, TEXT("Downmix must be a positive integer number"), &err);
dlg_check_get(hDlg, IDC_TAGFILE_DISABLE, &settings->tagfile_disable);
dlg_check_get(hDlg, IDC_FORCE_TITLE, &settings->force_title);
dlg_check_get(hDlg, IDC_EXTS_UNKNOWN_ON, &settings->exts_unknown_on);
dlg_check_get(hDlg, IDC_EXTS_COMMON_ON, &settings->exts_common_on);
dlg_combo_get(hDlg, IDC_GAIN_TYPE, (int*)&settings->gain_type);
dlg_combo_get(hDlg, IDC_CLIP_TYPE, (int*)&settings->clip_type);
/* exact 0 was allowed before (AKA "intro only") but confuses people and may result in unplayable files */
if (settings->loop_count <= 0)
settings->loop_count = 1;
return err ? 0 : 1;
}
static void dlg_save_form(HWND hDlg, winamp_settings_t* settings, int reset) {
cfg_slider_set(hDlg, IDC_THREAD_PRIORITY_SLIDER, IDC_THREAD_PRIORITY_TEXT, settings->thread_priority, 1, 7, dlg_priority_strings);
dlg_input_set_d(hDlg, IDC_FADE_TIME, settings->fade_time);
dlg_input_set_d(hDlg, IDC_FADE_DELAY, settings->fade_delay);
dlg_input_set_d(hDlg, IDC_LOOP_COUNT, settings->loop_count);
dlg_check_set(hDlg, IDC_LOOP_FOREVER, settings->loop_forever);
dlg_check_set(hDlg, IDC_IGNORE_LOOP, settings->ignore_loop);
dlg_check_set(hDlg, IDC_LOOP_NORMALLY, (!settings->loop_forever && !settings->ignore_loop));
dlg_check_set(hDlg, IDC_DISABLE_SUBSONGS, settings->disable_subsongs);
dlg_input_set_i(hDlg, IDC_DOWNMIX_CHANNELS, settings->downmix_channels);
dlg_check_set(hDlg, IDC_TAGFILE_DISABLE, settings->tagfile_disable);
dlg_check_set(hDlg, IDC_FORCE_TITLE, settings->force_title);
dlg_check_set(hDlg, IDC_EXTS_UNKNOWN_ON, settings->exts_unknown_on);
dlg_check_set(hDlg, IDC_EXTS_COMMON_ON, settings->exts_common_on);
cfg_combo_set(hDlg, IDC_GAIN_TYPE, settings->gain_type, dlg_replaygain_strings, (reset ? 0 : 3));
cfg_combo_set(hDlg, IDC_CLIP_TYPE, settings->clip_type, dlg_replaygain_strings, (reset ? 0 : 3));
}
/* config dialog handler */
INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static int priority;
/* globals to get them a bit controlled */
winamp_settings_t* settings_ext = &settings;
In_Module* input_module_ext = &input_module;
switch (uMsg) {
case WM_CLOSE: /* hide dialog */
EndDialog(hDlg,TRUE);
return TRUE;
case WM_INITDIALOG: /* open dialog: load form with current settings */
priority = settings_ext->thread_priority;
dlg_save_form(hDlg, settings_ext, 0);
break;
case WM_COMMAND: /* button presses */
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
case IDOK: { /* read and verify new values, save and close */
int ok;
settings_ext->thread_priority = priority;
ok = dlg_load_form(hDlg, settings_ext);
if (!ok) break; /* this leaves values changed though */
save_config(input_module_ext, settings_ext);
EndDialog(hDlg,TRUE);
break;
}
case IDCANCEL: /* cancel dialog */
EndDialog(hDlg,TRUE);
break;
case IDC_DEFAULT_BUTTON: { /* reset values */
priority = defaults.thread_priority;
dlg_save_form(hDlg, &defaults, 1);
/* we don't save settings here as user can still cancel the dialog */
break;
}
case IDC_LOG_BUTTON: { /* shows log */
TCHAR wbuf[257*32];
char buf[257*32];
size_t buf_size = 257*32;
int i, max = 0;
const char** lines = logger_get_lines(&max);
/* could use some nice scrollable text but I don't know arcane Windows crap */
if (lines == NULL) {
snprintf(buf, 257, "%s\n", "couldn't read log");
}
else if (max == 0) {
snprintf(buf, 257, "%s\n", "(empty)");
}
else {
//todo improve
char* tmp = buf;
for (i = 0; i < max; i++) {
int done = snprintf(tmp, 256, "%s", lines[i]);
if (done < 0 || done >= 256)
break;
tmp += (done); // + 1
}
}
cfg_char_to_wchar(wbuf, buf_size, buf);
MessageBox(hDlg, buf, TEXT("vgmstream log"), MB_OK);
break;
}
default:
return FALSE;
}
return FALSE;
case WM_HSCROLL: /* priority scroll */
if ((struct HWND__*)lParam == GetDlgItem(hDlg, IDC_THREAD_PRIORITY_SLIDER)) {
if (LOWORD(wParam) == TB_THUMBPOSITION || LOWORD(wParam) == TB_THUMBTRACK) {
priority = HIWORD(wParam)-1;
}
else {
priority = SendMessage(GetDlgItem(hDlg,IDC_THREAD_PRIORITY_SLIDER),TBM_GETPOS,0,0)-1;
}
SetDlgItemText(hDlg, IDC_THREAD_PRIORITY_TEXT, dlg_priority_strings[priority]);
}
break;
default:
return FALSE;
}
return TRUE;
}
/* ************************************* */
/* IN_LOG */
/* ************************************* */
/* could just write to file but to avoid leaving temp crap just log to memory and print what when requested */
winamp_log_t* walog;
#define WALOG_MAX_LINES 32
#define WALOG_MAX_CHARS 256
struct winamp_log_t {
char data[WALOG_MAX_LINES * WALOG_MAX_CHARS];
int logged;
const char* lines[WALOG_MAX_LINES];
} ;
void logger_init() {
walog = malloc(sizeof(winamp_log_t));
if (!walog) return;
walog->logged = 0;
}
void logger_free() {
free(walog);
walog = NULL;
}
/* logs to data as a sort of circular buffer. example if max_lines is 6:
* - log 0 = "msg1"
* ...
* - log 5 = "msg5" > limit reached, next will overwrite 0
* - log 0 = "msg6" (max 6 logs, but can only write las 6)
* - when requested lines should go from current to: 1,2,3,4,5,0
*/
void logger_callback(int level, const char* str) {
char* buf;
int pos;
if (!walog)
return;
pos = (walog->logged % WALOG_MAX_LINES) * WALOG_MAX_CHARS;
buf = &walog->data[pos];
snprintf(buf, WALOG_MAX_CHARS, "%s", str);
walog->logged++;
/* ??? */
if (walog->logged >= 0x7FFFFFFF)
walog->logged = 0;
}
const char** logger_get_lines(int* p_max) {
int i, from, max;
if (!walog) {
*p_max = 0;
return NULL;
}
if (walog->logged > WALOG_MAX_LINES) {
from = (walog->logged % WALOG_MAX_LINES);
max = WALOG_MAX_LINES;
}
else {
from = 0;
max = walog->logged;
}
for (i = 0; i < max; i++) {
int pos = ((from + i) % WALOG_MAX_LINES) * WALOG_MAX_CHARS;
walog->lines[i] = &walog->data[pos];
}
*p_max = max;
return walog->lines;
}