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