1 /*
2  * Copyright (c) 2011 Tim van der Molen <tim@kariliq.nl>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 /* Let NetBSD expose strtonum(). */
18 #define _OPENBSD_SOURCE
19 
20 #ifdef __OpenBSD__
21 #include <sys/tree.h>
22 #else
23 #include "compat/tree.h"
24 #endif
25 
26 #include <limits.h>
27 #include <pthread.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <strings.h>
31 
32 #include "siren.h"
33 
34 /*
35  * Maximum size of any string created by option_attrib_to_string(). The longest
36  * possible string is "blink,bold,dim,reverse,standout,underline", which
37  * contains 42 characters (including the terminating NUL byte).
38  */
39 #define OPTION_ATTRIB_MAXLEN 42
40 
41 struct option_entry {
42 	char			*name;
43 	enum option_type	 type;
44 	union {
45 		struct {
46 			int		 cur;
47 			int		 min;
48 			int		 max;
49 		} number;
50 
51 		struct format		*format;
52 		int			 colour;
53 		int			 attrib;
54 		int			 boolean;
55 		char			*string;
56 	} value;
57 	void			 (*callback)(void);
58 
59 	RB_ENTRY(option_entry)	 entries;
60 };
61 
62 RB_HEAD(option_tree, option_entry);
63 
64 static int			 option_cmp_entry(struct option_entry *,
65 				    struct option_entry *);
66 static void			 option_insert_entry(struct option_entry *);
67 
68 RB_PROTOTYPE(option_tree, option_entry, entries, option_cmp_entry)
69 
70 static struct option_tree	option_tree = RB_INITIALIZER(option_tree);
71 static pthread_mutex_t		option_tree_mtx = PTHREAD_MUTEX_INITIALIZER;
72 
73 static const struct {
74 	const int		 attrib;
75 	const char		*name;
76 } option_attribs[] = {
77 	{ ATTRIB_BLINK,		"blink" },
78 	{ ATTRIB_BOLD,		"bold" },
79 	{ ATTRIB_DIM,		"dim" },
80 	{ ATTRIB_NORMAL,	"normal" },
81 	{ ATTRIB_REVERSE,	"reverse" },
82 	{ ATTRIB_STANDOUT,	"standout" },
83 	{ ATTRIB_UNDERLINE,	"underline" }
84 };
85 
86 static const struct {
87 	const enum colour	 colour;
88 	const char		*name;
89 } option_colours[] = {
90 	{ COLOUR_BLACK,		"black" },
91 	{ COLOUR_BLUE,		"blue" },
92 	{ COLOUR_CYAN,		"cyan" },
93 	{ COLOUR_DEFAULT,	"default" },
94 	{ COLOUR_GREEN,		"green" },
95 	{ COLOUR_MAGENTA,	"magenta" },
96 	{ COLOUR_RED,		"red" },
97 	{ COLOUR_WHITE,		"white" },
98 	{ COLOUR_YELLOW,	"yellow" }
99 };
100 
101 static const struct {
102 	const int		 boolean;
103 	const char		*name;
104 } option_booleans[] = {
105 	{ 0,			"false" },
106 	{ 1,			"true" },
107 	/* Aliases for the above two names. */
108 	{ 0,			"0" },
109 	{ 0,			"off" },
110 	{ 0,			"no" },
111 	{ 1,			"1" },
112 	{ 1,			"on" },
113 	{ 1,			"yes" }
114 };
115 
RB_GENERATE(option_tree,option_entry,entries,option_cmp_entry)116 RB_GENERATE(option_tree, option_entry, entries, option_cmp_entry)
117 
118 static void
119 option_add_attrib(const char *name, int value)
120 {
121 	struct option_entry *o;
122 
123 	o = xmalloc(sizeof *o);
124 	o->name = xstrdup(name);
125 	o->type = OPTION_TYPE_ATTRIB;
126 	o->value.attrib = value;
127 	o->callback = screen_configure_objects;
128 	option_insert_entry(o);
129 }
130 
131 static void
option_add_boolean(const char * name,int value,void (* callback)(void))132 option_add_boolean(const char *name, int value, void (*callback)(void))
133 {
134 	struct option_entry *o;
135 
136 	o = xmalloc(sizeof *o);
137 	o->name = xstrdup(name);
138 	o->type = OPTION_TYPE_BOOLEAN;
139 	o->value.boolean = value;
140 	o->callback = callback;
141 	option_insert_entry(o);
142 }
143 
144 static void
option_add_colour(const char * name,int value)145 option_add_colour(const char *name, int value)
146 {
147 	struct option_entry *o;
148 
149 	o = xmalloc(sizeof *o);
150 	o->name = xstrdup(name);
151 	o->type = OPTION_TYPE_COLOUR;
152 	o->value.colour = value;
153 	o->callback = screen_configure_objects;
154 	option_insert_entry(o);
155 }
156 
157 static void
option_add_format(const char * name,const char * fmt,void (* callback)(void))158 option_add_format(const char *name, const char *fmt, void (*callback)(void))
159 {
160 	struct option_entry *o;
161 
162 	o = xmalloc(sizeof *o);
163 	o->name = xstrdup(name);
164 	o->type = OPTION_TYPE_FORMAT;
165 	o->value.format = format_parse(fmt);
166 	o->callback = callback;
167 	option_insert_entry(o);
168 }
169 
170 void
option_add_number(const char * name,int value,int minvalue,int maxvalue,void (* callback)(void))171 option_add_number(const char *name, int value, int minvalue, int maxvalue,
172     void (*callback)(void))
173 {
174 	struct option_entry *o;
175 
176 #ifdef DEBUG
177 	if (minvalue > maxvalue)
178 		LOG_FATALX("%s: minimum value larger than maximum value",
179 		    name);
180 	if (value < minvalue || value > maxvalue)
181 		LOG_FATALX("%s: initial value not within range", name);
182 #endif
183 
184 	o = xmalloc(sizeof *o);
185 	o->name = xstrdup(name);
186 	o->type = OPTION_TYPE_NUMBER;
187 	o->value.number.cur = value;
188 	o->value.number.min = minvalue;
189 	o->value.number.max = maxvalue;
190 	o->callback = callback;
191 	option_insert_entry(o);
192 }
193 
194 void
option_add_string(const char * name,const char * value,void (* callback)(void))195 option_add_string(const char *name, const char *value, void (*callback)(void))
196 {
197 	struct option_entry *o;
198 
199 	o = xmalloc(sizeof *o);
200 	o->name = xstrdup(name);
201 	o->type = OPTION_TYPE_STRING;
202 	o->value.string = xstrdup(value);
203 	o->callback = callback;
204 	option_insert_entry(o);
205 }
206 
207 char *
option_attrib_to_string(int attrib)208 option_attrib_to_string(int attrib)
209 {
210 	size_t	i;
211 	char	str[OPTION_ATTRIB_MAXLEN];
212 
213 	str[0] = '\0';
214 	for (i = 0; i < NELEMENTS(option_attribs); i++)
215 		if (attrib & option_attribs[i].attrib ||
216 		    attrib == option_attribs[i].attrib) {
217 			if (str[0] != '\0')
218 				strlcat(str, ",", sizeof str);
219 			strlcat(str, option_attribs[i].name, sizeof str);
220 		}
221 
222 	return xstrdup(str);
223 }
224 
225 const char *
option_boolean_to_string(int boolean)226 option_boolean_to_string(int boolean)
227 {
228 	size_t i;
229 
230 	for (i = 0; i < NELEMENTS(option_booleans); i++)
231 		if (boolean == option_booleans[i].boolean)
232 			return option_booleans[i].name;
233 
234 	LOG_FATALX("unknown boolean");
235 }
236 
237 static int
option_cmp_entry(struct option_entry * o1,struct option_entry * o2)238 option_cmp_entry(struct option_entry *o1, struct option_entry *o2)
239 {
240 	return strcmp(o1->name, o2->name);
241 }
242 
243 char *
option_colour_to_string(int colour)244 option_colour_to_string(int colour)
245 {
246 	size_t	 i;
247 	char	*str;
248 
249 	if (colour >= 0) {
250 		xasprintf(&str, "colour%d", colour);
251 		return str;
252 	}
253 
254 	for (i = 0; i < NELEMENTS(option_colours); i++)
255 		if (colour == option_colours[i].colour)
256 			return xstrdup(option_colours[i].name);
257 
258 	LOG_FATALX("unknown colour: %d", colour);
259 }
260 
261 void
option_end(void)262 option_end(void)
263 {
264 	struct option_entry *o;
265 
266 	while ((o = RB_ROOT(&option_tree)) != NULL) {
267 		RB_REMOVE(option_tree, &option_tree, o);
268 		free(o->name);
269 		switch (o->type) {
270 		case OPTION_TYPE_FORMAT:
271 			format_free(o->value.format);
272 			break;
273 		case OPTION_TYPE_STRING:
274 			free(o->value.string);
275 			break;
276 		/* Silence gcc. */
277 		default:
278 			break;
279 		}
280 		free(o);
281 	}
282 }
283 
284 /*
285  * The option_tree_mtx mutex must be locked before calling this function.
286  */
287 static struct option_entry *
option_find(const char * name)288 option_find(const char *name)
289 {
290 	struct option_entry find, *o;
291 
292 	find.name = xstrdup(name);
293 	o = RB_FIND(option_tree, &option_tree, &find);
294 	free(find.name);
295 	return o;
296 }
297 
298 /*
299  * The option_tree_mtx mutex must be locked before calling this function.
300  */
301 static struct option_entry *
option_find_type(const char * name,enum option_type type)302 option_find_type(const char *name, enum option_type type)
303 {
304 	struct option_entry *o;
305 
306 	if ((o = option_find(name)) == NULL)
307 		LOG_FATALX("%s: option does not exist", name);
308 	if (o->type != type)
309 		LOG_FATALX("%s: option is not of expected type", name);
310 	return o;
311 }
312 
313 const char *
option_format_to_string(const struct format * format)314 option_format_to_string(const struct format *format)
315 {
316 	return format_to_string(format);
317 }
318 
319 int
option_get_attrib(const char * name)320 option_get_attrib(const char *name)
321 {
322 	struct option_entry	*o;
323 	int			 attrib;
324 
325 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
326 	o = option_find_type(name, OPTION_TYPE_ATTRIB);
327 	attrib = o->value.attrib;
328 	XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
329 	return attrib;
330 }
331 
332 int
option_get_boolean(const char * name)333 option_get_boolean(const char *name)
334 {
335 	struct option_entry	*o;
336 	int			 boolean;
337 
338 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
339 	o = option_find_type(name, OPTION_TYPE_BOOLEAN);
340 	boolean = o->value.boolean;
341 	XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
342 	return boolean;
343 }
344 
345 int
option_get_colour(const char * name)346 option_get_colour(const char *name)
347 {
348 	struct option_entry	*o;
349 	int			 colour;
350 
351 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
352 	o = option_find_type(name, OPTION_TYPE_COLOUR);
353 	colour = o->value.colour;
354 	XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
355 	return colour;
356 }
357 
358 /*
359  * option_lock() must be called before calling this function, and
360  * option_unlock() must be called afterwards.
361  */
362 struct format *
option_get_format(const char * name)363 option_get_format(const char *name)
364 {
365 	struct option_entry	*o;
366 	struct format		*format;
367 
368 	o = option_find_type(name, OPTION_TYPE_FORMAT);
369 	format = o->value.format;
370 	return format;
371 }
372 
373 int
option_get_number(const char * name)374 option_get_number(const char *name)
375 {
376 	struct option_entry	*o;
377 	int			 number;
378 
379 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
380 	o = option_find_type(name, OPTION_TYPE_NUMBER);
381 	number = o->value.number.cur;
382 	XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
383 	return number;
384 }
385 
386 void
option_get_number_range(const char * name,int * min,int * max)387 option_get_number_range(const char *name, int *min, int *max)
388 {
389 	struct option_entry *o;
390 
391 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
392 	o = option_find_type(name, OPTION_TYPE_NUMBER);
393 	*min = o->value.number.min;
394 	*max = o->value.number.max;
395 	XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
396 }
397 
398 char *
option_get_string(const char * name)399 option_get_string(const char *name)
400 {
401 	struct option_entry	*o;
402 	char			*string;
403 
404 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
405 	o = option_find_type(name, OPTION_TYPE_STRING);
406 	string = xstrdup(o->value.string);
407 	XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
408 	return string;
409 }
410 
411 int
option_get_type(const char * name,enum option_type * type)412 option_get_type(const char *name, enum option_type *type)
413 {
414 	struct option_entry	*o;
415 	int			 ret;
416 
417 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
418 	if ((o = option_find(name)) == NULL)
419 		ret = -1;
420 	else {
421 		*type = o->type;
422 		ret = 0;
423 	}
424 	XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
425 	return ret;
426 }
427 
428 void
option_init(void)429 option_init(void)
430 {
431 	option_add_boolean("continue", 1, player_print);
432 	option_add_boolean("continue-after-error", 0, NULL);
433 	option_add_format("library-format", "%-*a %-*l %4y %2n. %-*t %5d",
434 	    library_print);
435 	option_add_format("library-format-alt", "%-*F %5d", library_print);
436 	option_add_string("output-plugin", "default", player_change_op);
437 	option_add_format("player-status-format",
438 	    "%-7s  %5p / %5d  %3v%%  %u%{?c,  continue,}%{?r,  repeat-all,}"
439 	    "%{?t,  repeat-track,}", player_print);
440 	option_add_format("player-track-format", "%a - %l (%y) - %n. %t",
441 	    player_print);
442 	option_add_format("player-track-format-alt", "%F", player_print);
443 	option_add_format("playlist-format", "%-*a %-*t %5d", playlist_print);
444 	option_add_format("playlist-format-alt", "%-*F %5d", playlist_print);
445 	option_add_format("queue-format", "%-*a %-*t %5d", queue_print);
446 	option_add_format("queue-format-alt", "%-*F %5d", queue_print);
447 	option_add_boolean("repeat-all", 1, player_print);
448 	option_add_boolean("repeat-track", 0, player_print);
449 	option_add_boolean("show-all-files", 0, browser_refresh_dir);
450 	option_add_boolean("show-cursor", 0, screen_configure_cursor);
451 	option_add_boolean("show-hidden-files", 0, browser_refresh_dir);
452 
453 	option_add_attrib("active-attr", ATTRIB_NORMAL);
454 	option_add_colour("active-bg", COLOUR_DEFAULT);
455 	option_add_colour("active-fg", COLOUR_YELLOW);
456 	option_add_attrib("error-attr", ATTRIB_NORMAL);
457 	option_add_colour("error-bg", COLOUR_DEFAULT);
458 	option_add_colour("error-fg", COLOUR_RED);
459 	option_add_attrib("info-attr", ATTRIB_NORMAL);
460 	option_add_colour("info-bg", COLOUR_DEFAULT);
461 	option_add_colour("info-fg", COLOUR_CYAN);
462 	option_add_attrib("player-attr", ATTRIB_REVERSE);
463 	option_add_colour("player-bg", COLOUR_DEFAULT);
464 	option_add_colour("player-fg", COLOUR_DEFAULT);
465 	option_add_attrib("prompt-attr", ATTRIB_NORMAL);
466 	option_add_colour("prompt-bg", COLOUR_DEFAULT);
467 	option_add_colour("prompt-fg", COLOUR_DEFAULT);
468 	option_add_attrib("selection-attr", ATTRIB_REVERSE);
469 	option_add_colour("selection-bg", COLOUR_WHITE);
470 	option_add_colour("selection-fg", COLOUR_BLUE);
471 	option_add_attrib("status-attr", ATTRIB_NORMAL);
472 	option_add_colour("status-bg", COLOUR_DEFAULT);
473 	option_add_colour("status-fg", COLOUR_DEFAULT);
474 	option_add_attrib("view-attr", ATTRIB_NORMAL);
475 	option_add_colour("view-bg", COLOUR_DEFAULT);
476 	option_add_colour("view-fg", COLOUR_DEFAULT);
477 	option_add_attrib("view-title-attr", ATTRIB_REVERSE);
478 	option_add_colour("view-title-bg", COLOUR_DEFAULT);
479 	option_add_colour("view-title-fg", COLOUR_DEFAULT);
480 }
481 
482 static void
option_insert_entry(struct option_entry * o)483 option_insert_entry(struct option_entry *o)
484 {
485 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
486 	if (RB_INSERT(option_tree, &option_tree, o) != NULL)
487 		LOG_FATALX("%s: option already exists", o->name);
488 	XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
489 }
490 
491 void
option_lock(void)492 option_lock(void)
493 {
494 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
495 }
496 
497 void
option_set_attrib(const char * name,int value)498 option_set_attrib(const char *name, int value)
499 {
500 	struct option_entry *o;
501 
502 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
503 	o = option_find_type(name, OPTION_TYPE_ATTRIB);
504 	if (value == o->value.attrib)
505 		XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
506 	else {
507 		o->value.attrib = value;
508 		XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
509 		if (o->callback != NULL)
510 			o->callback();
511 	}
512 }
513 
514 void
option_set_boolean(const char * name,int value)515 option_set_boolean(const char *name, int value)
516 {
517 	struct option_entry *o;
518 
519 #ifdef DEBUG
520 	if (value != 0 && value != 1)
521 		LOG_FATALX("%s: %d: invalid value", name, value);
522 #endif
523 
524 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
525 	o = option_find_type(name, OPTION_TYPE_BOOLEAN);
526 	if (value == o->value.boolean)
527 		XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
528 	else {
529 		o->value.boolean = value;
530 		XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
531 		if (o->callback != NULL)
532 			o->callback();
533 	}
534 }
535 
536 void
option_set_colour(const char * name,int value)537 option_set_colour(const char *name, int value)
538 {
539 	struct option_entry *o;
540 
541 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
542 	o = option_find_type(name, OPTION_TYPE_COLOUR);
543 	if (value == o->value.colour)
544 		XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
545 	else {
546 		o->value.colour = value;
547 		XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
548 		if (o->callback != NULL)
549 			o->callback();
550 	}
551 }
552 
553 void
option_set_format(const char * name,struct format * format)554 option_set_format(const char *name, struct format *format)
555 {
556 	struct option_entry *o;
557 
558 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
559 	o = option_find_type(name, OPTION_TYPE_FORMAT);
560 	format_free(o->value.format);
561 	o->value.format = format;
562 	XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
563 	if (o->callback != NULL)
564 		o->callback();
565 }
566 
567 void
option_set_number(const char * name,int value)568 option_set_number(const char *name, int value)
569 {
570 	struct option_entry *o;
571 
572 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
573 	o = option_find_type(name, OPTION_TYPE_NUMBER);
574 	if (value == o->value.number.cur || value < o->value.number.min ||
575 	    value > o->value.number.max)
576 		XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
577 	else {
578 		o->value.number.cur = value;
579 		XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
580 		if (o->callback != NULL)
581 			o->callback();
582 	}
583 }
584 
585 void
option_set_string(const char * name,const char * value)586 option_set_string(const char *name, const char *value)
587 {
588 	struct option_entry *o;
589 
590 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
591 	o = option_find_type(name, OPTION_TYPE_STRING);
592 	if (!strcmp(value, o->value.string))
593 		XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
594 	else {
595 		free(o->value.string);
596 		o->value.string = xstrdup(value);
597 		XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
598 		if (o->callback != NULL)
599 			o->callback();
600 	}
601 }
602 
603 int
option_string_to_attrib(const char * name)604 option_string_to_attrib(const char *name)
605 {
606 	size_t i;
607 
608 	for (i = 0; i < NELEMENTS(option_attribs); i++)
609 		if (!strcasecmp(name, option_attribs[i].name))
610 			return option_attribs[i].attrib;
611 	return -1;
612 }
613 
614 int
option_string_to_boolean(const char * name)615 option_string_to_boolean(const char *name)
616 {
617 	size_t i;
618 
619 	for (i = 0; i < NELEMENTS(option_booleans); i++)
620 		if (!strcasecmp(name, option_booleans[i].name))
621 			return option_booleans[i].boolean;
622 	return -1;
623 }
624 
625 int
option_string_to_colour(const char * name,int * colour)626 option_string_to_colour(const char *name, int *colour)
627 {
628 	size_t		 i;
629 	const char	*errstr;
630 
631 	if (!strncasecmp(name, "colour", 6)) {
632 		*colour = strtonum(name + 6, 0, INT_MAX, &errstr);
633 		return errstr == NULL ? 0 : -1;
634 	}
635 
636 	for (i = 0; i < NELEMENTS(option_colours); i++)
637 		if (!strcasecmp(name, option_colours[i].name)) {
638 			*colour = option_colours[i].colour;
639 			return 0;
640 		}
641 
642 	return -1;
643 }
644 
645 void
option_toggle_boolean(const char * name)646 option_toggle_boolean(const char *name)
647 {
648 	struct option_entry *o;
649 
650 	XPTHREAD_MUTEX_LOCK(&option_tree_mtx);
651 	o = option_find_type(name, OPTION_TYPE_BOOLEAN);
652 	o->value.boolean = !o->value.boolean;
653 	XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
654 	if (o->callback != NULL)
655 		o->callback();
656 }
657 
658 void
option_unlock(void)659 option_unlock(void)
660 {
661 	XPTHREAD_MUTEX_UNLOCK(&option_tree_mtx);
662 }
663