mirror of https://github.com/xemu-project/xemu.git
ui: Add xemu settings subsystem
This commit is contained in:
parent
8632c5d1fa
commit
7752ed32bf
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* xemu Settings Management
|
||||
*
|
||||
* Copyright (C) 2020 Matt Borgerson
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <SDL_filesystem.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "xemu-settings.h"
|
||||
#include "inih/ini.c" // FIXME
|
||||
|
||||
enum config_types {
|
||||
CONFIG_TYPE_STRING,
|
||||
CONFIG_TYPE_INT,
|
||||
CONFIG_TYPE_BOOL,
|
||||
CONFIG_TYPE_ENUM,
|
||||
CONFIG_TYPE__MAX
|
||||
};
|
||||
|
||||
struct xemu_settings {
|
||||
// [system]
|
||||
char *flash_path;
|
||||
char *bootrom_path;
|
||||
char *hdd_path;
|
||||
char *dvd_path;
|
||||
char *eeprom_path;
|
||||
int memory;
|
||||
int short_animation; // Boolean
|
||||
|
||||
// [display]
|
||||
int scale;
|
||||
|
||||
// [input]
|
||||
char *controller_1_guid;
|
||||
char *controller_2_guid;
|
||||
char *controller_3_guid;
|
||||
char *controller_4_guid;
|
||||
};
|
||||
|
||||
struct enum_str_map {
|
||||
int value;
|
||||
const char *str;
|
||||
};
|
||||
|
||||
static const struct enum_str_map display_scale_map[DISPLAY_SCALE__COUNT+1] = {
|
||||
{ DISPLAY_SCALE_CENTER, "center" },
|
||||
{ DISPLAY_SCALE_SCALE, "scale" },
|
||||
{ DISPLAY_SCALE_STRETCH, "stretch" },
|
||||
{ 0, NULL },
|
||||
};
|
||||
|
||||
struct config_offset_table {
|
||||
enum config_types type;
|
||||
const char *section;
|
||||
const char *name;
|
||||
ptrdiff_t offset;
|
||||
union {
|
||||
const char *default_str;
|
||||
int default_int;
|
||||
int default_bool;
|
||||
};
|
||||
const struct enum_str_map *enum_map;
|
||||
} config_items[XEMU_SETTINGS__COUNT] = {
|
||||
// Please keep organized by section
|
||||
[XEMU_SETTINGS_SYSTEM_FLASH_PATH] = { CONFIG_TYPE_STRING, "system", "flash_path", offsetof(struct xemu_settings, flash_path), { .default_str = "" } },
|
||||
[XEMU_SETTINGS_SYSTEM_BOOTROM_PATH] = { CONFIG_TYPE_STRING, "system", "bootrom_path", offsetof(struct xemu_settings, bootrom_path), { .default_str = "" } },
|
||||
[XEMU_SETTINGS_SYSTEM_HDD_PATH] = { CONFIG_TYPE_STRING, "system", "hdd_path", offsetof(struct xemu_settings, hdd_path), { .default_str = "" } },
|
||||
[XEMU_SETTINGS_SYSTEM_DVD_PATH] = { CONFIG_TYPE_STRING, "system", "dvd_path", offsetof(struct xemu_settings, dvd_path), { .default_str = "" } },
|
||||
[XEMU_SETTINGS_SYSTEM_EEPROM_PATH] = { CONFIG_TYPE_STRING, "system", "eeprom_path", offsetof(struct xemu_settings, eeprom_path), { .default_str = "" } },
|
||||
[XEMU_SETTINGS_SYSTEM_MEMORY] = { CONFIG_TYPE_INT, "system", "memory", offsetof(struct xemu_settings, memory), { .default_int = 64 } },
|
||||
[XEMU_SETTINGS_SYSTEM_SHORTANIM] = { CONFIG_TYPE_BOOL, "system", "shortanim", offsetof(struct xemu_settings, short_animation), { .default_bool = 0 } },
|
||||
|
||||
[XEMU_SETTINGS_DISPLAY_SCALE] = { CONFIG_TYPE_ENUM, "display", "scale", offsetof(struct xemu_settings, scale), { .default_int = DISPLAY_SCALE_SCALE }, display_scale_map },
|
||||
|
||||
[XEMU_SETTINGS_INPUT_CONTROLLER_1_GUID] = { CONFIG_TYPE_STRING, "input", "controller_1_guid", offsetof(struct xemu_settings, controller_1_guid), { .default_str = "" } },
|
||||
[XEMU_SETTINGS_INPUT_CONTROLLER_2_GUID] = { CONFIG_TYPE_STRING, "input", "controller_2_guid", offsetof(struct xemu_settings, controller_2_guid), { .default_str = "" } },
|
||||
[XEMU_SETTINGS_INPUT_CONTROLLER_3_GUID] = { CONFIG_TYPE_STRING, "input", "controller_3_guid", offsetof(struct xemu_settings, controller_3_guid), { .default_str = "" } },
|
||||
[XEMU_SETTINGS_INPUT_CONTROLLER_4_GUID] = { CONFIG_TYPE_STRING, "input", "controller_4_guid", offsetof(struct xemu_settings, controller_4_guid), { .default_str = "" } },
|
||||
};
|
||||
|
||||
static const char *settings_path;
|
||||
static const char *filename = "xemu.ini";
|
||||
static struct xemu_settings *g_settings;
|
||||
static int settings_failed_to_load = 0;
|
||||
|
||||
static void *xemu_settings_get_field(enum xemu_settings_keys key, enum config_types type)
|
||||
{
|
||||
assert(key < XEMU_SETTINGS__COUNT);
|
||||
assert(config_items[key].type == type);
|
||||
return (void *)((char*)g_settings + config_items[key].offset);
|
||||
}
|
||||
|
||||
int xemu_settings_set_string(enum xemu_settings_keys key, const char *str)
|
||||
{
|
||||
char **field_str = (char **)xemu_settings_get_field(key, CONFIG_TYPE_STRING);
|
||||
free(*field_str);
|
||||
*field_str = strdup(str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xemu_settings_get_string(enum xemu_settings_keys key, const char **str)
|
||||
{
|
||||
*str = *(const char **)xemu_settings_get_field(key, CONFIG_TYPE_STRING);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xemu_settings_set_int(enum xemu_settings_keys key, int val)
|
||||
{
|
||||
int *field_int = (int *)xemu_settings_get_field(key, CONFIG_TYPE_INT);
|
||||
*field_int = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xemu_settings_get_int(enum xemu_settings_keys key, int *val)
|
||||
{
|
||||
*val = *(int *)xemu_settings_get_field(key, CONFIG_TYPE_INT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xemu_settings_set_bool(enum xemu_settings_keys key, int val)
|
||||
{
|
||||
int *field_int = (int *)xemu_settings_get_field(key, CONFIG_TYPE_BOOL);
|
||||
*field_int = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xemu_settings_get_bool(enum xemu_settings_keys key, int *val)
|
||||
{
|
||||
*val = *(int *)xemu_settings_get_field(key, CONFIG_TYPE_BOOL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xemu_settings_set_enum(enum xemu_settings_keys key, int val)
|
||||
{
|
||||
int *field_int = (int *)xemu_settings_get_field(key, CONFIG_TYPE_ENUM);
|
||||
*field_int = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xemu_settings_get_enum(enum xemu_settings_keys key, int *val)
|
||||
{
|
||||
*val = *(int *)xemu_settings_get_field(key, CONFIG_TYPE_ENUM);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *xemu_settings_get_path(void)
|
||||
{
|
||||
if (settings_path != NULL) {
|
||||
return settings_path;
|
||||
}
|
||||
|
||||
// Note: Ideally SDL_GetPrefPath should be used here to determine where the
|
||||
// settings file should be stored. However, until xemu gains a proper
|
||||
// installer, assume it will be run in "portable mode" such that everything
|
||||
// needed to run is all in the same directory, or specified explicitly by
|
||||
// the user via config file.
|
||||
#if 0
|
||||
char *base = SDL_GetPrefPath("xemu", "xemu");
|
||||
#else
|
||||
// char *base = SDL_GetBasePath();
|
||||
// if (base == NULL) {
|
||||
// base = strdup("./");
|
||||
// }
|
||||
char *base = strdup("./");
|
||||
#endif
|
||||
assert(base != NULL);
|
||||
size_t base_len = strlen(base);
|
||||
size_t filename_len = strlen(filename);
|
||||
size_t len = base_len + filename_len + 1;
|
||||
char *path = malloc(len);
|
||||
memcpy(path, base, base_len);
|
||||
free(base);
|
||||
memcpy(path+base_len, filename, strlen(filename));
|
||||
path[len-1] = '\0';
|
||||
settings_path = path;
|
||||
|
||||
fprintf(stderr, "%s: config path: %s\n", __func__, settings_path);
|
||||
|
||||
return settings_path;
|
||||
}
|
||||
|
||||
static int xemu_enum_str_to_int(const struct enum_str_map *map, const char *str, int *value)
|
||||
{
|
||||
for (int i = 0; map[i].str != NULL; i++) {
|
||||
if (strcmp(map[i].str, str) == 0) {
|
||||
*value = map[i].value;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int xemu_enum_int_to_str(const struct enum_str_map *map, int value, const char **str)
|
||||
{
|
||||
for (int i = 0; map[i].str != NULL; i++) {
|
||||
if (map[i].value == value) {
|
||||
*str = map[i].str;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static enum xemu_settings_keys xemu_key_from_name(const char *section, const char *name)
|
||||
{
|
||||
for (int i = 0; i < XEMU_SETTINGS__COUNT; i++) {
|
||||
if ((strcmp(section, config_items[i].section) == 0) &&
|
||||
(strcmp(name, config_items[i].name) == 0)) {
|
||||
return i; // Found
|
||||
}
|
||||
}
|
||||
|
||||
return XEMU_SETTINGS_INVALID;
|
||||
}
|
||||
|
||||
static int config_parse_callback(void *user, const char *section, const char *name, const char *value)
|
||||
{
|
||||
// struct xemu_settings *settings = (struct xemu_settings *)user;
|
||||
fprintf(stderr, "%s: [%s] %s = %s\n", __func__, section, name, value);
|
||||
|
||||
enum xemu_settings_keys key = xemu_key_from_name(section, name);
|
||||
|
||||
if (key == XEMU_SETTINGS_INVALID) {
|
||||
fprintf(stderr, "Ignoring unknown key %s.%s\n", section, name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (config_items[key].type == CONFIG_TYPE_STRING) {
|
||||
xemu_settings_set_string(key, value);
|
||||
} else if (config_items[key].type == CONFIG_TYPE_INT) {
|
||||
int int_val;
|
||||
int converted = sscanf(value, "%d", &int_val);
|
||||
if (converted != 1) {
|
||||
fprintf(stderr, "Error parsing %s.%s as integer. Got '%s'\n", section, name, value);
|
||||
return 0;
|
||||
}
|
||||
xemu_settings_set_int(key, int_val);
|
||||
} else if (config_items[key].type == CONFIG_TYPE_BOOL) {
|
||||
int int_val;
|
||||
if (strcmp(value, "true") == 0) {
|
||||
int_val = 1;
|
||||
} else if (strcmp(value, "false") == 0) {
|
||||
int_val = 0;
|
||||
} else {
|
||||
fprintf(stderr, "Error parsing %s.%s as boolean. Got '%s'\n", section, name, value);
|
||||
return 0;
|
||||
}
|
||||
xemu_settings_set_bool(key, int_val);
|
||||
} else if (config_items[key].type == CONFIG_TYPE_ENUM) {
|
||||
int int_val;
|
||||
int status = xemu_enum_str_to_int(config_items[key].enum_map, value, &int_val);
|
||||
if (status != 0) {
|
||||
fprintf(stderr, "Error parsing %s.%s as enum. Got '%s'\n", section, name, value);
|
||||
return 0;
|
||||
}
|
||||
xemu_settings_set_enum(key, int_val);
|
||||
} else {
|
||||
// Unimplemented
|
||||
assert(0);
|
||||
}
|
||||
|
||||
// Success
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void xemu_settings_init_default(struct xemu_settings *settings)
|
||||
{
|
||||
memset(settings, 0, sizeof(struct xemu_settings));
|
||||
for (int i = 0; i < XEMU_SETTINGS__COUNT; i++) {
|
||||
if (config_items[i].type == CONFIG_TYPE_STRING) {
|
||||
xemu_settings_set_string(i, config_items[i].default_str);
|
||||
} else if (config_items[i].type == CONFIG_TYPE_INT) {
|
||||
xemu_settings_set_int(i, config_items[i].default_int);
|
||||
} else if (config_items[i].type == CONFIG_TYPE_BOOL) {
|
||||
xemu_settings_set_bool(i, config_items[i].default_bool);
|
||||
} else if (config_items[i].type == CONFIG_TYPE_ENUM) {
|
||||
xemu_settings_set_enum(i, config_items[i].default_int);
|
||||
} else {
|
||||
// Unimplemented
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int xemu_settings_did_fail_to_load(void)
|
||||
{
|
||||
return settings_failed_to_load;
|
||||
}
|
||||
|
||||
void xemu_settings_load(void)
|
||||
{
|
||||
// Should only call this once, at startup
|
||||
assert(g_settings == NULL);
|
||||
|
||||
g_settings = malloc(sizeof(struct xemu_settings));
|
||||
assert(g_settings != NULL);
|
||||
xemu_settings_init_default(g_settings);
|
||||
|
||||
// Parse configuration file
|
||||
int status = ini_parse(xemu_settings_get_path(),
|
||||
config_parse_callback,
|
||||
g_settings);
|
||||
if (status < 0) {
|
||||
// fprintf(stderr, "Failed to load config! Using defaults\n");
|
||||
settings_failed_to_load = 1;
|
||||
}
|
||||
|
||||
printf("Config loaded!\n");
|
||||
}
|
||||
|
||||
int xemu_settings_save(void)
|
||||
{
|
||||
FILE *fd = fopen(xemu_settings_get_path(), "wb");
|
||||
assert(fd != NULL);
|
||||
|
||||
const char *last_section = "";
|
||||
for (int i = 0; i < XEMU_SETTINGS__COUNT; i++) {
|
||||
if (strcmp(last_section, config_items[i].section)) {
|
||||
fprintf(fd, "[%s]\n", config_items[i].section);
|
||||
last_section = config_items[i].section;
|
||||
}
|
||||
|
||||
fprintf(fd, "%s = ", config_items[i].name);
|
||||
if (config_items[i].type == CONFIG_TYPE_STRING) {
|
||||
const char *v;
|
||||
xemu_settings_get_string(i, &v);
|
||||
fprintf(fd, "%s\n", v);
|
||||
} else if (config_items[i].type == CONFIG_TYPE_INT) {
|
||||
int v;
|
||||
xemu_settings_get_int(i, &v);
|
||||
fprintf(fd, "%d\n", v);
|
||||
} else if (config_items[i].type == CONFIG_TYPE_BOOL) {
|
||||
int v;
|
||||
xemu_settings_get_bool(i, &v);
|
||||
fprintf(fd, "%s\n", !!(v) ? "true" : "false");
|
||||
} else if (config_items[i].type == CONFIG_TYPE_ENUM) {
|
||||
int v;
|
||||
xemu_settings_get_enum(i, &v);
|
||||
const char *str = "";
|
||||
xemu_enum_int_to_str(config_items[i].enum_map, v, &str);
|
||||
fprintf(fd, "%s\n", str);
|
||||
} else {
|
||||
// Unimplemented
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fd);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* xemu Settings Management
|
||||
*
|
||||
* Primary storage for non-volatile user configuration. Basic key-value storage
|
||||
* that gets saved to an INI file. All entries should be accessed through the
|
||||
* appropriate getter/setter functions.
|
||||
*
|
||||
* Copyright (C) 2020 Matt Borgerson
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef XEMU_SETTINGS_H
|
||||
#define XEMU_SETTINGS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum xemu_settings_keys {
|
||||
XEMU_SETTINGS_SYSTEM_FLASH_PATH,
|
||||
XEMU_SETTINGS_SYSTEM_BOOTROM_PATH,
|
||||
XEMU_SETTINGS_SYSTEM_HDD_PATH,
|
||||
XEMU_SETTINGS_SYSTEM_EEPROM_PATH,
|
||||
XEMU_SETTINGS_SYSTEM_DVD_PATH,
|
||||
XEMU_SETTINGS_SYSTEM_MEMORY,
|
||||
XEMU_SETTINGS_SYSTEM_SHORTANIM,
|
||||
XEMU_SETTINGS_DISPLAY_SCALE,
|
||||
XEMU_SETTINGS_INPUT_CONTROLLER_1_GUID,
|
||||
XEMU_SETTINGS_INPUT_CONTROLLER_2_GUID,
|
||||
XEMU_SETTINGS_INPUT_CONTROLLER_3_GUID,
|
||||
XEMU_SETTINGS_INPUT_CONTROLLER_4_GUID,
|
||||
XEMU_SETTINGS__COUNT,
|
||||
XEMU_SETTINGS_INVALID = -1
|
||||
};
|
||||
|
||||
enum DISPLAY_SCALE
|
||||
{
|
||||
DISPLAY_SCALE_CENTER,
|
||||
DISPLAY_SCALE_SCALE,
|
||||
DISPLAY_SCALE_STRETCH,
|
||||
DISPLAY_SCALE__COUNT,
|
||||
DISPLAY_SCALE_INVALID = -1
|
||||
};
|
||||
|
||||
// Determine whether settings were loaded or not
|
||||
int xemu_settings_did_fail_to_load(void);
|
||||
|
||||
// Get path of the config file on disk
|
||||
const char *xemu_settings_get_path(void);
|
||||
|
||||
// Load config file from disk, or load defaults
|
||||
void xemu_settings_load(void);
|
||||
|
||||
// Save config file to disk
|
||||
int xemu_settings_save(void);
|
||||
|
||||
// Config item setters/getters
|
||||
int xemu_settings_set_string(enum xemu_settings_keys key, const char *str);
|
||||
int xemu_settings_get_string(enum xemu_settings_keys key, const char **str);
|
||||
int xemu_settings_set_int(enum xemu_settings_keys key, int val);
|
||||
int xemu_settings_get_int(enum xemu_settings_keys key, int *val);
|
||||
int xemu_settings_set_bool(enum xemu_settings_keys key, int val);
|
||||
int xemu_settings_get_bool(enum xemu_settings_keys key, int *val);
|
||||
int xemu_settings_set_enum(enum xemu_settings_keys key, int val);
|
||||
int xemu_settings_get_enum(enum xemu_settings_keys key, int *val);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue