diff options
Diffstat (limited to 'config.c')
-rw-r--r-- | config.c | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/config.c b/config.c new file mode 100644 index 0000000..30a1bf2 --- /dev/null +++ b/config.c @@ -0,0 +1,539 @@ +#include "config.h" +#include "util.h" + +#include <ctype.h> +#include <iniparser.h> +#include <math.h> + +#ifdef SNDIO +#include <sndio.h> +#endif + +#include <stdarg.h> +#include <stdbool.h> +#include <sys/stat.h> + +double smoothDef[5] = {1, 1, 1, 1, 1}; + +enum input_method default_methods[] = { + INPUT_FIFO, + INPUT_PORTAUDIO, + INPUT_ALSA, + INPUT_PULSE, +}; + +char *outputMethod, *channels; + +const char *input_method_names[] = { + "fifo", "portaudio", "alsa", "pulse", "sndio", "shmem", +}; + +const bool has_input_method[] = { + true, /** Always have at least FIFO and shmem input. */ + HAS_PORTAUDIO, HAS_ALSA, HAS_PULSE, HAS_SNDIO, true, +}; + +enum input_method input_method_by_name(const char *str) { + for (int i = 0; i < INPUT_MAX; i++) { + if (!strcmp(str, input_method_names[i])) { + return (enum input_method)i; + } + } + + return INPUT_MAX; +} + +void write_errorf(void *err, const char *fmt, ...) { + struct error_s *error = (struct error_s *)err; + va_list args; + va_start(args, fmt); + error->length += + vsnprintf((char *)error->message + error->length, MAX_ERROR_LEN - error->length, fmt, args); + va_end(args); +} + +int validate_color(char *checkColor, int om, void *err) { + struct error_s *error = (struct error_s *)err; + int validColor = 0; + if (checkColor[0] == '#' && strlen(checkColor) == 7) { + // If the output mode is not ncurses, tell the user to use a named colour instead of hex + // colours. + if (om != OUTPUT_NCURSES) { + write_errorf( + error, "Only 'ncurses' output method supports HTML colors. Please change " + "the colours or the output method.\nAs of version 0.7.0 ncurses is no longer" + " the default output method\n"); + return 0; + } + // 0 to 9 and a to f + for (int i = 1; checkColor[i]; ++i) { + if (!isdigit(checkColor[i])) { + if (tolower(checkColor[i]) >= 'a' && tolower(checkColor[i]) <= 'f') { + validColor = 1; + } else { + validColor = 0; + break; + } + } else { + validColor = 1; + } + } + } else { + if ((strcmp(checkColor, "black") == 0) || (strcmp(checkColor, "red") == 0) || + (strcmp(checkColor, "green") == 0) || (strcmp(checkColor, "yellow") == 0) || + (strcmp(checkColor, "blue") == 0) || (strcmp(checkColor, "magenta") == 0) || + (strcmp(checkColor, "cyan") == 0) || (strcmp(checkColor, "white") == 0) || + (strcmp(checkColor, "default") == 0)) + validColor = 1; + } + return validColor; +} + +bool validate_colors(void *params, void *err) { + struct config_params *p = (struct config_params *)params; + struct error_s *error = (struct error_s *)err; + + // validate: color + if (!validate_color(p->color, p->om, error)) { + write_errorf(error, "The value for 'foreground' is invalid. It can be either one of the 7 " + "named colors or a HTML color of the form '#xxxxxx'.\n"); + return false; + } + + // validate: background color + if (!validate_color(p->bcolor, p->om, error)) { + write_errorf(error, "The value for 'background' is invalid. It can be either one of the 7 " + "named colors or a HTML color of the form '#xxxxxx'.\n"); + return false; + } + + if (p->gradient) { + for (int i = 0; i < p->gradient_count; i++) { + if (!validate_color(p->gradient_colors[i], p->om, error)) { + write_errorf( + error, + "Gradient color %d is invalid. It must be HTML color of the form '#xxxxxx'.\n", + i + 1); + return false; + } + } + } + + // In case color is not html format set bgcol and col to predefinedint values + p->col = -1; + if (strcmp(p->color, "black") == 0) + p->col = 0; + if (strcmp(p->color, "red") == 0) + p->col = 1; + if (strcmp(p->color, "green") == 0) + p->col = 2; + if (strcmp(p->color, "yellow") == 0) + p->col = 3; + if (strcmp(p->color, "blue") == 0) + p->col = 4; + if (strcmp(p->color, "magenta") == 0) + p->col = 5; + if (strcmp(p->color, "cyan") == 0) + p->col = 6; + if (strcmp(p->color, "white") == 0) + p->col = 7; + // default if invalid + + // validate: background color + if (strcmp(p->bcolor, "black") == 0) + p->bgcol = 0; + if (strcmp(p->bcolor, "red") == 0) + p->bgcol = 1; + if (strcmp(p->bcolor, "green") == 0) + p->bgcol = 2; + if (strcmp(p->bcolor, "yellow") == 0) + p->bgcol = 3; + if (strcmp(p->bcolor, "blue") == 0) + p->bgcol = 4; + if (strcmp(p->bcolor, "magenta") == 0) + p->bgcol = 5; + if (strcmp(p->bcolor, "cyan") == 0) + p->bgcol = 6; + if (strcmp(p->bcolor, "white") == 0) + p->bgcol = 7; + // default if invalid + + return true; +} + +bool validate_config(struct config_params *p, struct error_s *error) { + // validate: output method + p->om = OUTPUT_NOT_SUPORTED; + if (strcmp(outputMethod, "ncurses") == 0) { + p->om = OUTPUT_NCURSES; + p->bgcol = -1; +#ifndef NCURSES + write_errorf(error, "cava was built without ncurses support, install ncursesw dev files " + "and run make clean && ./configure && make again\n"); + return false; +#endif + } + if (strcmp(outputMethod, "noncurses") == 0) { + p->om = OUTPUT_NONCURSES; + p->bgcol = 0; + } + if (strcmp(outputMethod, "raw") == 0) { // raw: + p->om = OUTPUT_RAW; + p->bar_spacing = 0; + p->bar_width = 1; + + // checking data format + p->is_bin = -1; + if (strcmp(p->data_format, "binary") == 0) { + p->is_bin = 1; + // checking bit format: + if (p->bit_format != 8 && p->bit_format != 16) { + write_errorf( + error, + "bit format %d is not supported, supported data formats are: '8' and '16'\n", + p->bit_format); + return false; + } + } else if (strcmp(p->data_format, "ascii") == 0) { + p->is_bin = 0; + if (p->ascii_range < 1) { + write_errorf(error, "ascii max value must be a positive integer\n"); + return false; + } + } else { + write_errorf(error, + "data format %s is not supported, supported data formats are: 'binary' " + "and 'ascii'\n", + p->data_format); + return false; + } + } + if (p->om == OUTPUT_NOT_SUPORTED) { +#ifndef NCURSES + write_errorf( + error, + "output method %s is not supported, supported methods are: 'noncurses' and 'raw'\n", + outputMethod); + return false; +#endif + +#ifdef NCURSES + write_errorf(error, + "output method %s is not supported, supported methods are: 'ncurses', " + "'noncurses' and 'raw'\n", + outputMethod); + return false; +#endif + } + + // validate: output channels + p->stereo = -1; + if (strcmp(channels, "mono") == 0) { + p->stereo = 0; + if (strcmp(p->mono_option, "average") != 0 && strcmp(p->mono_option, "left") != 0 && + strcmp(p->mono_option, "right") != 0) { + + write_errorf(error, + "mono option %s is not supported, supported options are: 'average', " + "'left' or 'right'\n", + p->mono_option); + return false; + } + } + if (strcmp(channels, "stereo") == 0) + p->stereo = 1; + if (p->stereo == -1) { + write_errorf( + error, + "output channels %s is not supported, supported channelss are: 'mono' and 'stereo'\n", + channels); + return false; + } + + // validate: bars + p->autobars = 1; + if (p->fixedbars > 0) + p->autobars = 0; + if (p->fixedbars > 256) + p->fixedbars = 256; + if (p->bar_width > 256) + p->bar_width = 256; + if (p->bar_width < 1) + p->bar_width = 1; + + // validate: framerate + if (p->framerate < 0) { + write_errorf(error, "framerate can't be negative!\n"); + return false; + } + + if (!validate_colors(p, error)) { + return false; + } + + // validate: gravity + p->gravity = p->gravity / 100; + if (p->gravity < 0) { + p->gravity = 0; + } + + // validate: integral + p->integral = p->integral / 100; + if (p->integral < 0) { + p->integral = 0; + } else if (p->integral > 1) { + p->integral = 1; + } + + // validate: cutoff + if (p->lower_cut_off == 0) + p->lower_cut_off++; + if (p->lower_cut_off > p->upper_cut_off) { + write_errorf(error, + "lower cutoff frequency can't be higher than higher cutoff frequency\n"); + return false; + } + + // setting sens + p->sens = p->sens / 100; + + // validate FFT buffer + if (p->FFTbufferSize >= 8 && p->FFTbufferSize <= 16) { + p->FFTbufferSize = pow(2, p->FFTbufferSize); + } else { + write_errorf(error, "FFT buffer is set in the exponent of 2 and must be between 8 - 16\n"); + return false; + } + + return true; +} + +bool load_colors(struct config_params *p, dictionary *ini, void *err) { + struct error_s *error = (struct error_s *)err; + + free(p->color); + free(p->bcolor); + + p->color = strdup(iniparser_getstring(ini, "color:foreground", "default")); + p->bcolor = strdup(iniparser_getstring(ini, "color:background", "default")); + + p->gradient = iniparser_getint(ini, "color:gradient", 0); + if (p->gradient) { + for (int i = 0; i < p->gradient_count; ++i) { + free(p->gradient_colors[i]); + } + p->gradient_count = iniparser_getint(ini, "color:gradient_count", 8); + if (p->gradient_count < 2) { + write_errorf(error, "\nAtleast two colors must be given as gradient!\n"); + return false; + } + if (p->gradient_count > 8) { + write_errorf(error, "\nMaximum 8 colors can be specified as gradient!\n"); + return false; + } + p->gradient_colors = (char **)malloc(sizeof(char *) * p->gradient_count * 9); + p->gradient_colors[0] = + strdup(iniparser_getstring(ini, "color:gradient_color_1", "#59cc33")); + p->gradient_colors[1] = + strdup(iniparser_getstring(ini, "color:gradient_color_2", "#80cc33")); + p->gradient_colors[2] = + strdup(iniparser_getstring(ini, "color:gradient_color_3", "#a6cc33")); + p->gradient_colors[3] = + strdup(iniparser_getstring(ini, "color:gradient_color_4", "#cccc33")); + p->gradient_colors[4] = + strdup(iniparser_getstring(ini, "color:gradient_color_5", "#cca633")); + p->gradient_colors[5] = + strdup(iniparser_getstring(ini, "color:gradient_color_6", "#cc8033")); + p->gradient_colors[6] = + strdup(iniparser_getstring(ini, "color:gradient_color_7", "#cc5933")); + p->gradient_colors[7] = + strdup(iniparser_getstring(ini, "color:gradient_color_8", "#cc3333")); + } + return true; +} + +bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colorsOnly, + struct error_s *error) { + FILE *fp; + + // config: creating path to default config file + if (configPath[0] == '\0') { + char *configFile = "config"; + char *configHome = getenv("XDG_CONFIG_HOME"); + if (configHome != NULL) { + sprintf(configPath, "%s/%s/", configHome, PACKAGE); + } else { + configHome = getenv("HOME"); + if (configHome != NULL) { + sprintf(configPath, "%s/%s/", configHome, ".config"); + mkdir(configPath, 0777); + sprintf(configPath, "%s/%s/%s/", configHome, ".config", PACKAGE); + } else { + write_errorf(error, "No HOME found (ERR_HOMELESS), exiting..."); + return false; + } + } + + // config: create directory + mkdir(configPath, 0777); + + // config: adding default filename file + strcat(configPath, configFile); + + fp = fopen(configPath, "ab+"); + if (fp) { + fclose(fp); + } else { + write_errorf(error, "Unable to access config '%s', exiting...\n", configPath); + return false; + } + + } else { // opening specified file + + fp = fopen(configPath, "rb+"); + if (fp) { + fclose(fp); + } else { + write_errorf(error, "Unable to open file '%s', exiting...\n", configPath); + return false; + } + } + + // config: parse ini + dictionary *ini; + ini = iniparser_load(configPath); + + if (colorsOnly) { + if (!load_colors(p, ini, error)) { + return false; + } + return validate_colors(p, error); + } + +#ifdef NCURSES + outputMethod = (char *)iniparser_getstring(ini, "output:method", "noncurses"); +#endif +#ifndef NCURSES + outputMethod = (char *)iniparser_getstring(ini, "output:method", "noncurses"); +#endif + + p->monstercat = 1.5 * iniparser_getdouble(ini, "smoothing:monstercat", 0); + p->waves = iniparser_getint(ini, "smoothing:waves", 0); + p->integral = iniparser_getdouble(ini, "smoothing:integral", 77); + p->gravity = iniparser_getdouble(ini, "smoothing:gravity", 100); + p->ignore = iniparser_getdouble(ini, "smoothing:ignore", 0); + + if (!load_colors(p, ini, error)) { + return false; + } + + p->fixedbars = iniparser_getint(ini, "general:bars", 0); + p->bar_width = iniparser_getint(ini, "general:bar_width", 2); + p->bar_spacing = iniparser_getint(ini, "general:bar_spacing", 1); + p->framerate = iniparser_getint(ini, "general:framerate", 60); + p->sens = iniparser_getint(ini, "general:sensitivity", 100); + p->autosens = iniparser_getint(ini, "general:autosens", 1); + p->overshoot = iniparser_getint(ini, "general:overshoot", 20); + p->lower_cut_off = iniparser_getint(ini, "general:lower_cutoff_freq", 50); + p->upper_cut_off = iniparser_getint(ini, "general:higher_cutoff_freq", 10000); + p->FFTbufferSize = iniparser_getint(ini, "general:FFTbufferSize", 12); + + // config: output + free(channels); + free(p->mono_option); + free(p->raw_target); + free(p->data_format); + + channels = strdup(iniparser_getstring(ini, "output:channels", "stereo")); + p->mono_option = strdup(iniparser_getstring(ini, "output:mono_option", "average")); + p->raw_target = strdup(iniparser_getstring(ini, "output:raw_target", "/dev/stdout")); + p->data_format = strdup(iniparser_getstring(ini, "output:data_format", "binary")); + p->bar_delim = (char)iniparser_getint(ini, "output:bar_delimiter", 59); + p->frame_delim = (char)iniparser_getint(ini, "output:frame_delimiter", 10); + p->ascii_range = iniparser_getint(ini, "output:ascii_max_range", 1000); + p->bit_format = iniparser_getint(ini, "output:bit_format", 16); + + // read & validate: eq + p->userEQ_keys = iniparser_getsecnkeys(ini, "eq"); + if (p->userEQ_keys > 0) { + p->userEQ_enabled = 1; + p->userEQ = calloc(p->userEQ_keys + 1, sizeof(p->userEQ)); +#ifndef LEGACYINIPARSER + const char *keys[p->userEQ_keys]; + iniparser_getseckeys(ini, "eq", keys); +#endif +#ifdef LEGACYINIPARSER + char **keys = iniparser_getseckeys(ini, "eq"); +#endif + for (int sk = 0; sk < p->userEQ_keys; sk++) { + p->userEQ[sk] = iniparser_getdouble(ini, keys[sk], 1); + } + } else { + p->userEQ_enabled = 0; + } + + free(p->audio_source); + + char *input_method_name; + for (size_t i = 0; i < ARRAY_SIZE(default_methods); i++) { + enum input_method method = default_methods[i]; + if (has_input_method[method]) { + input_method_name = + (char *)iniparser_getstring(ini, "input:method", input_method_names[method]); + } + } + + p->im = input_method_by_name(input_method_name); + switch (p->im) { +#ifdef ALSA + case INPUT_ALSA: + p->audio_source = strdup(iniparser_getstring(ini, "input:source", "hw:Loopback,1")); + break; +#endif + case INPUT_FIFO: + p->audio_source = strdup(iniparser_getstring(ini, "input:source", "/tmp/mpd.fifo")); + p->fifoSample = iniparser_getint(ini, "input:sample_rate", 44100); + p->fifoSampleBits = iniparser_getint(ini, "input:sample_bits", 16); + break; +#ifdef PULSE + case INPUT_PULSE: + p->audio_source = strdup(iniparser_getstring(ini, "input:source", "auto")); + break; +#endif +#ifdef SNDIO + case INPUT_SNDIO: + p->audio_source = strdup(iniparser_getstring(ini, "input:source", SIO_DEVANY)); + break; +#endif + case INPUT_SHMEM: + p->audio_source = + strdup(iniparser_getstring(ini, "input:source", "/squeezelite-00:00:00:00:00:00")); + break; +#ifdef PORTAUDIO + case INPUT_PORTAUDIO: + p->audio_source = strdup(iniparser_getstring(ini, "input:source", "auto")); + break; +#endif + case INPUT_MAX: { + char supported_methods[255] = ""; + for (int i = 0; i < INPUT_MAX; i++) { + if (has_input_method[i]) { + strcat(supported_methods, "'"); + strcat(supported_methods, input_method_names[i]); + strcat(supported_methods, "' "); + } + } + write_errorf(error, "input method '%s' is not supported, supported methods are: %s\n", + input_method_name, supported_methods); + return false; + } + default: + write_errorf(error, "cava was built without '%s' input support\n", + input_method_names[p->im]); + return false; + } + + bool result = validate_config(p, error); + iniparser_freedict(ini); + return result; +} |