view 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
line wrap: on
line source

/**
 * config.c:
 *
 * Functions to load/save/edit the config.
**/

#include "dirtools.h"
#include "config.h"
#include "ini.h"
#include "plugin.h" /* g_plugin */

/* bypass intptr_t check */
#ifndef _MSC_VER
#define _MSC_VER 1201
#define WGSDK_UGLY_MSC_HACK 1
#endif

#include <Winamp/wa_ipc.h>

#ifdef WGSDK_UGLY_MSC_HACK
#undef _MSC_VER
#endif

#include <shlwapi.h>
#include <shlobj.h>

#include <assert.h>

#define MAX_LINE_LENGTH 256

#ifndef IPC_GETPLUGINDIRECTORYW
/* requires Winamp 5.58+ */
#define IPC_GETPLUGINDIRECTORYW 1336
#endif

/* set defaults */
struct config config = {
	.display_title = 1,
	.display_artist_name = 1,
	.display_album_name = 1,
	.display_album_art = 1,
	.display_song_info = 1,
	.show_elapsed_time = 1
};

/* must be free'd by the caller */
LPWSTR cfg_get_path() {
	LPWSTR plugins_folder = NULL;
	DWORD winamp_version = SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETVERSION);

	if (winamp_version >= 0x5580) {
		/* Native wide string version of IPC_GETPLUGINDIRECTORY */
		LPCWSTR w_plugins_folder = (LPCWSTR)SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW);
		size_t len = wcslen(w_plugins_folder);

		plugins_folder = calloc(len + 1, sizeof(WCHAR));
		if (!plugins_folder)
			return NULL;

		wcsncpy(plugins_folder, w_plugins_folder, len);
	} else if (winamp_version >= 0x5110) {
		/* ANSI string version of IPC_GETPLUGINDIRECTORY, convert */
		LPCSTR plugins_folder_ansi = (LPCSTR)SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORY);
		int required_size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, plugins_folder_ansi, -1, NULL, 0);
		if (required_size < 0)
			return NULL;

		plugins_folder = calloc(required_size, sizeof(WCHAR));
		if (!plugins_folder)
			return NULL;

		int size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, plugins_folder_ansi, -1, plugins_folder, required_size);
		if (size < 0)
			return NULL;
	} else if (winamp_version >= 0x2900) {
		/* Ancient winamp, use IPC_GETINIDIRECTORY and find out from there */
		LPCSTR ini_directory = (LPCSTR)SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETINIDIRECTORY);

		int required_size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ini_directory, -1, NULL, 0);
		if (required_size < 0)
			return NULL;

		LPWSTR ini_directory_w = calloc(required_size, sizeof(WCHAR));
		if (!ini_directory_w)
			return NULL;

		int size = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ini_directory, -1, ini_directory_w, required_size);
		if (size < 0) {
			free(ini_directory_w);
			return NULL;
		}

		plugins_folder = dirtools_concat_paths(ini_directory_w, L"Plugins");
		free(ini_directory_w);
	} else {
		/* please, please for the love of God update winamp. */
		WCHAR appdata[MAX_PATH] = {L'\0'};

		HRESULT res = SHGetFolderPathW(
			g_plugin.hwndParent, CSIDL_APPDATA, NULL,
			SHGFP_TYPE_CURRENT, appdata
		);

		if (res != S_OK)
			return NULL;

		plugins_folder = dirtools_concat_paths(appdata, L"Winamp\\Plugins");
	}

	LPWSTR final = dirtools_concat_paths(plugins_folder, L"wgsdk");
	free(plugins_folder);

	return final;
}

static int cfg_ini_handler(void* data, const char* section, const char* key, const char* value) {
	struct config* config = (struct config*)data;

	char* ptr; // used with strtol

	if (!strcmp(section, "Display configuration")) {
		if (!strcmp(key, "Display title")) {
			config->display_title = !!strtol(value, &ptr, 10);
			return 1;
		} else if (!strcmp(key, "Display artist name")) {
			config->display_artist_name = !!strtol(value, &ptr, 10);
			return 1;
		} else if (!strcmp(key, "Display album name")) {
			config->display_album_name = !!strtol(value, &ptr, 10);
			return 1;
		} else if (!strcmp(key, "Display album art")) {
			config->display_album_art = !!strtol(value, &ptr, 10);
			return 1;
		} else if (!strcmp(key, "Display song information")) {
			config->display_song_info = !!strtol(value, &ptr, 10);
			return 1;
		} else if (!strcmp(key, "Show elapsed time")) {
			config->show_elapsed_time = !!strtol(value, &ptr, 10);
			return 1;
		} else return 0;
	} else return 0;
}

char* cfg_ini_reader(char* str, int num, void* stream) {
	HANDLE file = (HANDLE)stream;
	DWORD bytes_read;

	return (ReadFile(file, str, num, &bytes_read, NULL) && num == bytes_read) ? str : NULL;
}

int cfg_load(struct config* restrict config) {
	LPWSTR fold_path = cfg_get_path();
	assert(fold_path);

	LPWSTR path = dirtools_concat_paths(fold_path, L"config.ini");
	free(fold_path);

	HANDLE file = CreateFileW(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (file == INVALID_HANDLE_VALUE) /* file doesn't exist? */
		return 0;

	if (ini_parse_stream(cfg_ini_reader, file, cfg_ini_handler, config))
		return 1;

	CloseHandle(file);

	free(path);
	return 0;
}

void cfg_write_int_key(HANDLE file, const char* key, int value) {
	DWORD bytes_written;

	/* write the key */
	size_t len = strlen(key) * sizeof(*key);
	WriteFile(file, key, len, &bytes_written, NULL);
	assert(bytes_written == len);

	static const char equals[] = {'='};
	WriteFile(file, equals, sizeof(equals), &bytes_written, NULL);
	assert(bytes_written == sizeof(equals));

	char buf[16] = {'\0'};
	_itoa(value, buf, 10);
	len = strlen(buf) * sizeof(buf[0]);
	WriteFile(file, (buf), len, &bytes_written, NULL);
	assert(bytes_written == len);

	static const char ln[] = {'\r', '\n'};
	WriteFile(file, ln, sizeof(ln), &bytes_written, NULL);
	assert(bytes_written == sizeof(ln));
}

void cfg_write_header(HANDLE file, const char* header) {
	DWORD bytes_written = 0;

	static const char l_bracket[] = {'['};
	WriteFile(file, l_bracket, sizeof(l_bracket), &bytes_written, NULL);
	assert(bytes_written == sizeof(l_bracket));

	const size_t len = strlen(header) * sizeof(*header);
	WriteFile(file, header, len, &bytes_written, NULL);
	assert(bytes_written == len);

	static const char r_bracket[] = {']'};
	WriteFile(file, r_bracket, sizeof(r_bracket), &bytes_written, NULL);
	assert(bytes_written == sizeof(r_bracket));

	static const char ln[] = {'\r', '\n'};
	WriteFile(file, ln, sizeof(ln), &bytes_written, NULL);
	assert(bytes_written == sizeof(ln));
}

int cfg_save(const struct config* config) {
	LPWSTR fold_path = cfg_get_path();
	assert(fold_path);
	assert(!dirtools_create_directory(fold_path));

	LPWSTR path = dirtools_concat_paths(fold_path, L"config.ini");
	free(fold_path);

	/* dirty little hack that lets me not use goto */
	do {
		HANDLE file = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		if (file == INVALID_HANDLE_VALUE) /* huh */
			break;

		cfg_write_header(file, "Display configuration");

		cfg_write_int_key(file, "Display title", config->display_title);
		cfg_write_int_key(file, "Display artist name", config->display_artist_name);
		cfg_write_int_key(file, "Display album name", config->display_album_name);
		cfg_write_int_key(file, "Display album art", config->display_album_art);
		cfg_write_int_key(file, "Display song information", config->display_song_info);
		cfg_write_int_key(file, "Show elapsed time", config->show_elapsed_time);

		CloseHandle(file);
	} while (0);

	free(path);
	return 0;
}

#undef WritePrivateProfileIntW