1 #include "config.h"
2 #include "util.h"
3
4 #include <ctype.h>
5 #include <iniparser.h>
6 #include <math.h>
7
8 #ifdef SNDIO
9 #include <sndio.h>
10 #endif
11
12 #include <stdarg.h>
13 #include <stdbool.h>
14 #include <sys/stat.h>
15
16 double smoothDef[5] = {1, 1, 1, 1, 1};
17
18 enum input_method default_methods[] = {
19 INPUT_FIFO,
20 INPUT_PORTAUDIO,
21 INPUT_ALSA,
22 INPUT_PULSE,
23 };
24
25 char *outputMethod, *channels, *xaxisScale;
26
27 const char *input_method_names[] = {
28 "fifo", "portaudio", "alsa", "pulse", "sndio", "shmem",
29 };
30
31 const bool has_input_method[] = {
32 true, /** Always have at least FIFO and shmem input. */
33 HAS_PORTAUDIO, HAS_ALSA, HAS_PULSE, HAS_SNDIO, true,
34 };
35
input_method_by_name(const char * str)36 enum input_method input_method_by_name(const char *str) {
37 for (int i = 0; i < INPUT_MAX; i++) {
38 if (!strcmp(str, input_method_names[i])) {
39 return (enum input_method)i;
40 }
41 }
42
43 return INPUT_MAX;
44 }
45
write_errorf(void * err,const char * fmt,...)46 void write_errorf(void *err, const char *fmt, ...) {
47 struct error_s *error = (struct error_s *)err;
48 va_list args;
49 va_start(args, fmt);
50 error->length +=
51 vsnprintf((char *)error->message + error->length, MAX_ERROR_LEN - error->length, fmt, args);
52 va_end(args);
53 }
54
validate_color(char * checkColor,void * params,void * err)55 int validate_color(char *checkColor, void *params, void *err) {
56 struct config_params *p = (struct config_params *)params;
57 struct error_s *error = (struct error_s *)err;
58 int validColor = 0;
59 if (checkColor[0] == '#' && strlen(checkColor) == 7) {
60 // If the output mode is not ncurses, tell the user to use a named colour instead of hex
61 // colours.
62 if (p->output != OUTPUT_NCURSES) {
63 #ifdef NCURSES
64 write_errorf(error,
65 "hex color configured, but ncurses not set. Forcing ncurses mode.\n");
66 p->output = OUTPUT_NCURSES;
67 #else
68 write_errorf(error,
69 "Only 'ncurses' output method supports HTML colors "
70 "(required by gradient). "
71 "Cava was built without ncurses support, install ncurses(w) dev files "
72 "and rebuild.\n");
73 return 0;
74 #endif
75 }
76 // 0 to 9 and a to f
77 for (int i = 1; checkColor[i]; ++i) {
78 if (!isdigit(checkColor[i])) {
79 if (tolower(checkColor[i]) >= 'a' && tolower(checkColor[i]) <= 'f') {
80 validColor = 1;
81 } else {
82 validColor = 0;
83 break;
84 }
85 } else {
86 validColor = 1;
87 }
88 }
89 } else {
90 if ((strcmp(checkColor, "black") == 0) || (strcmp(checkColor, "red") == 0) ||
91 (strcmp(checkColor, "green") == 0) || (strcmp(checkColor, "yellow") == 0) ||
92 (strcmp(checkColor, "blue") == 0) || (strcmp(checkColor, "magenta") == 0) ||
93 (strcmp(checkColor, "cyan") == 0) || (strcmp(checkColor, "white") == 0) ||
94 (strcmp(checkColor, "default") == 0))
95 validColor = 1;
96 }
97 return validColor;
98 }
99
validate_colors(void * params,void * err)100 bool validate_colors(void *params, void *err) {
101 struct config_params *p = (struct config_params *)params;
102 struct error_s *error = (struct error_s *)err;
103
104 // validate: color
105 if (!validate_color(p->color, p, error)) {
106 write_errorf(error, "The value for 'foreground' is invalid. It can be either one of the 7 "
107 "named colors or a HTML color of the form '#xxxxxx'.\n");
108 return false;
109 }
110
111 // validate: background color
112 if (!validate_color(p->bcolor, p, error)) {
113 write_errorf(error, "The value for 'background' is invalid. It can be either one of the 7 "
114 "named colors or a HTML color of the form '#xxxxxx'.\n");
115 return false;
116 }
117
118 if (p->gradient) {
119 for (int i = 0; i < p->gradient_count; i++) {
120 if (!validate_color(p->gradient_colors[i], p, error)) {
121 write_errorf(
122 error,
123 "Gradient color %d is invalid. It must be HTML color of the form '#xxxxxx'.\n",
124 i + 1);
125 return false;
126 }
127 }
128 }
129
130 // In case color is not html format set bgcol and col to predefinedint values
131 p->col = -1;
132 if (strcmp(p->color, "black") == 0)
133 p->col = 0;
134 if (strcmp(p->color, "red") == 0)
135 p->col = 1;
136 if (strcmp(p->color, "green") == 0)
137 p->col = 2;
138 if (strcmp(p->color, "yellow") == 0)
139 p->col = 3;
140 if (strcmp(p->color, "blue") == 0)
141 p->col = 4;
142 if (strcmp(p->color, "magenta") == 0)
143 p->col = 5;
144 if (strcmp(p->color, "cyan") == 0)
145 p->col = 6;
146 if (strcmp(p->color, "white") == 0)
147 p->col = 7;
148 // default if invalid
149
150 // validate: background color
151 if (strcmp(p->bcolor, "black") == 0)
152 p->bgcol = 0;
153 if (strcmp(p->bcolor, "red") == 0)
154 p->bgcol = 1;
155 if (strcmp(p->bcolor, "green") == 0)
156 p->bgcol = 2;
157 if (strcmp(p->bcolor, "yellow") == 0)
158 p->bgcol = 3;
159 if (strcmp(p->bcolor, "blue") == 0)
160 p->bgcol = 4;
161 if (strcmp(p->bcolor, "magenta") == 0)
162 p->bgcol = 5;
163 if (strcmp(p->bcolor, "cyan") == 0)
164 p->bgcol = 6;
165 if (strcmp(p->bcolor, "white") == 0)
166 p->bgcol = 7;
167 // default if invalid
168
169 return true;
170 }
171
validate_config(struct config_params * p,struct error_s * error)172 bool validate_config(struct config_params *p, struct error_s *error) {
173 // validate: output method
174 p->output = OUTPUT_NOT_SUPORTED;
175 if (strcmp(outputMethod, "ncurses") == 0) {
176 p->output = OUTPUT_NCURSES;
177 p->bgcol = -1;
178 #ifndef NCURSES
179 write_errorf(error, "cava was built without ncurses support, install ncursesw dev files "
180 "and run make clean && ./configure && make again\n");
181 return false;
182 #endif
183 }
184 if (strcmp(outputMethod, "raw") == 0) { // raw:
185 p->output = OUTPUT_RAW;
186 p->bar_spacing = 0;
187 p->bar_width = 1;
188
189 // checking data format
190 p->is_bin = -1;
191 if (strcmp(p->data_format, "binary") == 0) {
192 p->is_bin = 1;
193 // checking bit format:
194 if (p->bit_format != 8 && p->bit_format != 16) {
195 write_errorf(
196 error,
197 "bit format %d is not supported, supported data formats are: '8' and '16'\n",
198 p->bit_format);
199 return false;
200 }
201 } else if (strcmp(p->data_format, "ascii") == 0) {
202 p->is_bin = 0;
203 if (p->ascii_range < 1) {
204 write_errorf(error, "ascii max value must be a positive integer\n");
205 return false;
206 }
207 } else {
208 write_errorf(error,
209 "data format %s is not supported, supported data formats are: 'binary' "
210 "and 'ascii'\n",
211 p->data_format);
212 return false;
213 }
214 }
215 if (p->output == OUTPUT_NOT_SUPORTED) {
216 #ifndef NCURSES
217 write_errorf(
218 error,
219 "output method %s is not supported, supported methods are: 'raw'\n",
220 outputMethod);
221 return false;
222 #endif
223
224 #ifdef NCURSES
225 write_errorf(error,
226 "output method %s is not supported, supported methods are: 'ncurses', "
227 "and 'raw'\n",
228 outputMethod);
229 return false;
230 #endif
231 }
232
233 p->xaxis = NONE;
234 if (strcmp(xaxisScale, "none") == 0) {
235 p->xaxis = NONE;
236 }
237 if (strcmp(xaxisScale, "frequency") == 0) {
238 p->xaxis = FREQUENCY;
239 }
240 if (strcmp(xaxisScale, "note") == 0) {
241 p->xaxis = NOTE;
242 }
243
244 // validate: output channels
245 p->stereo = -1;
246 if (strcmp(channels, "mono") == 0) {
247 p->stereo = 0;
248 if (strcmp(p->mono_option, "average") != 0 && strcmp(p->mono_option, "left") != 0 &&
249 strcmp(p->mono_option, "right") != 0) {
250
251 write_errorf(error,
252 "mono option %s is not supported, supported options are: 'average', "
253 "'left' or 'right'\n",
254 p->mono_option);
255 return false;
256 }
257 }
258 if (strcmp(channels, "stereo") == 0)
259 p->stereo = 1;
260 if (p->stereo == -1) {
261 write_errorf(
262 error,
263 "output channels %s is not supported, supported channelss are: 'mono' and 'stereo'\n",
264 channels);
265 return false;
266 }
267
268 // validate: bars
269 p->autobars = 1;
270 if (p->fixedbars > 0)
271 p->autobars = 0;
272 if (p->fixedbars > 256)
273 p->fixedbars = 256;
274 if (p->bar_width > 256)
275 p->bar_width = 256;
276 if (p->bar_width < 1)
277 p->bar_width = 1;
278
279 // validate: framerate
280 if (p->framerate < 0) {
281 write_errorf(error, "framerate can't be negative!\n");
282 return false;
283 }
284
285 // validate: colors
286 if (!validate_colors(p, error)) {
287 return false;
288 }
289
290 // validate: gravity
291 p->gravity = p->gravity / 100;
292 if (p->gravity < 0) {
293 p->gravity = 0;
294 }
295
296 // validate: integral
297 p->integral = p->integral / 100;
298 if (p->integral < 0) {
299 p->integral = 0;
300 } else if (p->integral > 1) {
301 p->integral = 1;
302 }
303
304 // validate: cutoff
305 if (p->lower_cut_off == 0)
306 p->lower_cut_off++;
307 if (p->lower_cut_off > p->upper_cut_off) {
308 write_errorf(error,
309 "lower cutoff frequency can't be higher than higher cutoff frequency\n");
310 return false;
311 }
312
313 // setting sens
314 p->sens = p->sens / 100;
315
316 return true;
317 }
318
load_colors(struct config_params * p,dictionary * ini,void * err)319 bool load_colors(struct config_params *p, dictionary *ini, void *err) {
320 struct error_s *error = (struct error_s *)err;
321
322 free(p->color);
323 free(p->bcolor);
324
325 p->color = strdup(iniparser_getstring(ini, "color:foreground", "default"));
326 p->bcolor = strdup(iniparser_getstring(ini, "color:background", "default"));
327
328 p->gradient = iniparser_getint(ini, "color:gradient", 0);
329 if (p->gradient) {
330 for (int i = 0; i < p->gradient_count; ++i) {
331 free(p->gradient_colors[i]);
332 }
333 p->gradient_count = iniparser_getint(ini, "color:gradient_count", 8);
334 if (p->gradient_count < 2) {
335 write_errorf(error, "\nAtleast two colors must be given as gradient!\n");
336 return false;
337 }
338 if (p->gradient_count > 8) {
339 write_errorf(error, "\nMaximum 8 colors can be specified as gradient!\n");
340 return false;
341 }
342 p->gradient_colors = (char **)malloc(sizeof(char *) * p->gradient_count * 9);
343 p->gradient_colors[0] =
344 strdup(iniparser_getstring(ini, "color:gradient_color_1", "#59cc33"));
345 p->gradient_colors[1] =
346 strdup(iniparser_getstring(ini, "color:gradient_color_2", "#80cc33"));
347 p->gradient_colors[2] =
348 strdup(iniparser_getstring(ini, "color:gradient_color_3", "#a6cc33"));
349 p->gradient_colors[3] =
350 strdup(iniparser_getstring(ini, "color:gradient_color_4", "#cccc33"));
351 p->gradient_colors[4] =
352 strdup(iniparser_getstring(ini, "color:gradient_color_5", "#cca633"));
353 p->gradient_colors[5] =
354 strdup(iniparser_getstring(ini, "color:gradient_color_6", "#cc8033"));
355 p->gradient_colors[6] =
356 strdup(iniparser_getstring(ini, "color:gradient_color_7", "#cc5933"));
357 p->gradient_colors[7] =
358 strdup(iniparser_getstring(ini, "color:gradient_color_8", "#cc3333"));
359 }
360 return true;
361 }
362
load_config(char configPath[PATH_MAX],struct config_params * p,bool colorsOnly,struct error_s * error)363 bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colorsOnly,
364 struct error_s *error) {
365 FILE *fp;
366
367 // config: creating path to default config file
368 if (configPath[0] == '\0') {
369 char *configFile = "config";
370 char *configHome = getenv("XDG_CONFIG_HOME");
371 if (configHome != NULL) {
372 sprintf(configPath, "%s/%s/", configHome, PACKAGE);
373 } else {
374 configHome = getenv("HOME");
375 if (configHome != NULL) {
376 sprintf(configPath, "%s/%s/", configHome, ".config");
377 mkdir(configPath, 0777);
378 sprintf(configPath, "%s/%s/%s/", configHome, ".config", PACKAGE);
379 } else {
380 write_errorf(error, "No HOME found (ERR_HOMELESS), exiting...");
381 return false;
382 }
383 }
384
385 // config: create directory
386 mkdir(configPath, 0777);
387
388 // config: adding default filename file
389 strcat(configPath, configFile);
390
391 // open file or create file if it does not exist
392 fp = fopen(configPath, "ab+");
393 if (fp) {
394 fclose(fp);
395 } else {
396 // try to open file read only
397 fp = fopen(configPath, "rb");
398 if (fp) {
399 fclose(fp);
400 } else {
401 write_errorf(error, "Unable to open or create file '%s', exiting...\n", configPath);
402 return false;
403 }
404 }
405
406 } else { // opening specified file
407
408 fp = fopen(configPath, "rb");
409 if (fp) {
410 fclose(fp);
411 } else {
412 write_errorf(error, "Unable to open file '%s', exiting...\n", configPath);
413 return false;
414 }
415 }
416
417 // config: parse ini
418 dictionary *ini;
419 ini = iniparser_load(configPath);
420
421 if (colorsOnly) {
422 if (!load_colors(p, ini, error)) {
423 return false;
424 }
425 return validate_colors(p, error);
426 }
427
428 #ifdef NCURSES
429 outputMethod = (char *)iniparser_getstring(ini, "output:method", "ncurses");
430 #endif
431 #ifndef NCURSES
432 outputMethod = (char *)iniparser_getstring(ini, "output:method", "noncurses");
433 #endif
434
435 xaxisScale = (char *)iniparser_getstring(ini, "output:xaxis", "none");
436 p->monstercat = 1.5 * iniparser_getdouble(ini, "smoothing:monstercat", 0);
437 p->waves = iniparser_getint(ini, "smoothing:waves", 0);
438 p->integral = iniparser_getdouble(ini, "smoothing:integral", 77);
439 p->gravity = iniparser_getdouble(ini, "smoothing:gravity", 100);
440 p->ignore = iniparser_getdouble(ini, "smoothing:ignore", 0);
441
442 if (!load_colors(p, ini, error)) {
443 return false;
444 }
445
446 p->fixedbars = iniparser_getint(ini, "general:bars", 0);
447 p->bar_width = iniparser_getint(ini, "general:bar_width", 2);
448 p->bar_spacing = iniparser_getint(ini, "general:bar_spacing", 1);
449 p->framerate = iniparser_getint(ini, "general:framerate", 60);
450 p->sens = iniparser_getint(ini, "general:sensitivity", 100);
451 p->autosens = iniparser_getint(ini, "general:autosens", 1);
452 p->overshoot = iniparser_getint(ini, "general:overshoot", 20);
453 p->lower_cut_off = iniparser_getint(ini, "general:lower_cutoff_freq", 50);
454 p->upper_cut_off = iniparser_getint(ini, "general:higher_cutoff_freq", 10000);
455 p->sleep_timer = iniparser_getint(ini, "general:sleep_timer", 0);
456
457 // config: output
458 free(channels);
459 free(p->mono_option);
460 free(p->raw_target);
461 free(p->data_format);
462
463 channels = strdup(iniparser_getstring(ini, "output:channels", "stereo"));
464 p->mono_option = strdup(iniparser_getstring(ini, "output:mono_option", "average"));
465 p->raw_target = strdup(iniparser_getstring(ini, "output:raw_target", "/dev/stdout"));
466 p->data_format = strdup(iniparser_getstring(ini, "output:data_format", "binary"));
467 p->bar_delim = (char)iniparser_getint(ini, "output:bar_delimiter", 59);
468 p->frame_delim = (char)iniparser_getint(ini, "output:frame_delimiter", 10);
469 p->ascii_range = iniparser_getint(ini, "output:ascii_max_range", 1000);
470 p->bit_format = iniparser_getint(ini, "output:bit_format", 16);
471
472 // read & validate: eq
473 p->userEQ_keys = iniparser_getsecnkeys(ini, "eq");
474 if (p->userEQ_keys > 0) {
475 p->userEQ_enabled = 1;
476 p->userEQ = (double *)calloc(p->userEQ_keys + 1, sizeof(double));
477 #ifndef LEGACYINIPARSER
478 const char *keys[p->userEQ_keys];
479 iniparser_getseckeys(ini, "eq", keys);
480 #endif
481 #ifdef LEGACYINIPARSER
482 char **keys = iniparser_getseckeys(ini, "eq");
483 #endif
484 for (int sk = 0; sk < p->userEQ_keys; sk++) {
485 p->userEQ[sk] = iniparser_getdouble(ini, keys[sk], 1);
486 }
487 } else {
488 p->userEQ_enabled = 0;
489 }
490
491 free(p->audio_source);
492
493 char *input_method_name;
494 for (size_t i = 0; i < ARRAY_SIZE(default_methods); i++) {
495 enum input_method method = default_methods[i];
496 if (has_input_method[method]) {
497 input_method_name =
498 (char *)iniparser_getstring(ini, "input:method", input_method_names[method]);
499 }
500 }
501
502 p->input = input_method_by_name(input_method_name);
503 switch (p->input) {
504 #ifdef ALSA
505 case INPUT_ALSA:
506 p->audio_source = strdup(iniparser_getstring(ini, "input:source", "hw:Loopback,1"));
507 break;
508 #endif
509 case INPUT_FIFO:
510 p->audio_source = strdup(iniparser_getstring(ini, "input:source", "/tmp/mpd.fifo"));
511 p->fifoSample = iniparser_getint(ini, "input:sample_rate", 44100);
512 p->fifoSampleBits = iniparser_getint(ini, "input:sample_bits", 16);
513 break;
514 #ifdef PULSE
515 case INPUT_PULSE:
516 p->audio_source = strdup(iniparser_getstring(ini, "input:source", "auto"));
517 break;
518 #endif
519 #ifdef SNDIO
520 case INPUT_SNDIO:
521 p->audio_source = strdup(iniparser_getstring(ini, "input:source", SIO_DEVANY));
522 break;
523 #endif
524 case INPUT_SHMEM:
525 p->audio_source =
526 strdup(iniparser_getstring(ini, "input:source", "/squeezelite-00:00:00:00:00:00"));
527 break;
528 #ifdef PORTAUDIO
529 case INPUT_PORTAUDIO:
530 p->audio_source = strdup(iniparser_getstring(ini, "input:source", "auto"));
531 break;
532 #endif
533 case INPUT_MAX: {
534 char supported_methods[255] = "";
535 for (int i = 0; i < INPUT_MAX; i++) {
536 if (has_input_method[i]) {
537 strcat(supported_methods, "'");
538 strcat(supported_methods, input_method_names[i]);
539 strcat(supported_methods, "' ");
540 }
541 }
542 write_errorf(error, "input method '%s' is not supported, supported methods are: %s\n",
543 input_method_name, supported_methods);
544 return false;
545 }
546 default:
547 write_errorf(error, "cava was built without '%s' input support\n",
548 input_method_names[p->input]);
549 return false;
550 }
551
552 bool result = validate_config(p, error);
553 iniparser_freedict(ini);
554 return result;
555 }
556