1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <unistd.h>
5#include <inttypes.h>
6#include <ctype.h>
7#include <errno.h>
8#include <pwd.h>
9#include CURSESINC
10#include "colors.h"
11#include "gettext_curses.h"
12#include "utils.h"
13#include "curskey.h"
14#include "bindings.h"
15#include "mixer_widget.h"
16
17#define ERROR_CONFIG (-1)
18#define ERROR_MISSING_ARGUMENTS (-2)
19#define ERROR_TOO_MUCH_ARGUMENTS (-3)
20
21static const char *error_message;
22static const char *error_cause;
23
24static int strlist_index(const char *haystack, unsigned int itemlen, const char *needle) {
25	unsigned int needle_len;
26	unsigned int pos;
27	const char *found;
28
29	needle_len = strlen(needle);
30	if (needle_len <= itemlen && needle[needle_len - 1] != ' ') {
31		found = strstr(haystack, needle);
32		if (found) {
33			pos = (found - haystack);
34			if (pos % itemlen == 0 && (needle_len == itemlen || haystack[pos+needle_len] == ' '))
35				return pos / itemlen;
36		}
37	}
38
39	return -1;
40}
41
42static int color_by_name(const char *name) {
43	return strlist_index(
44		"default"
45		"black  "
46		"red    "
47		"green  "
48		"yellow "
49		"blue   "
50		"magenta"
51		"cyan   "
52		"white  ", 7, name) - 1;
53};
54
55static int attr_by_name(const char *name) {
56	return (int[]) {
57		-1,
58		A_BOLD,
59		A_REVERSE,
60		A_STANDOUT,
61		A_DIM,
62		A_UNDERLINE,
63#ifdef A_ITALIC
64		A_ITALIC,
65#endif
66		A_NORMAL,
67		A_BLINK,
68	}[strlist_index(
69		"bold     "
70		"reverse  "
71		"standout "
72		"dim      "
73		"underline"
74#ifdef A_ITALIC
75		"italic   "
76#endif
77		"normal   "
78		"blink    ", 9, name) + 1];
79};
80
81#define W_NUMBER (1U << 0)
82
83enum textbox_word {
84	TW_BOTTOM = (1U << 1),
85	TW_CLOSE = (1U << 2),
86	TW_DOWN = (1U << 3),
87	TW_LEFT = (1U << 4),
88	TW_PAGE = (1U << 5),
89	TW_RIGHT = (1U << 6),
90	TW_TOP = (1U << 7),
91	TW_UP = (1U << 8),
92};
93
94const char *textbox_words =
95	"bottom"
96	"close "
97	"down  "
98	"left  "
99	"page  "
100	"right "
101	"top   "
102	"up    ";
103
104enum mixer_word {
105	MW_ALL = (1U << 1),
106	MW_BALANCE = (1U << 2),
107	MW_CAPTURE = (1U << 3),
108	MW_CARD = (1U << 4),
109	MW_CLOSE = (1U << 5),
110	MW_CONTROL = (1U << 6),
111	MW_DOWN = (1U << 7),
112	MW_FOCUS = (1U << 8),
113	MW_HELP = (1U << 9),
114	MW_INFORMATION = (1U << 10),
115	MW_LEFT = (1U << 11),
116	MW_MODE = (1U << 12),
117	MW_MUTE = (1U << 13),
118	MW_NEXT = (1U << 14),
119	MW_PLAYBACK = (1U << 15),
120	MW_PREVIOUS = (1U << 16),
121	MW_REFRESH = (1U << 17),
122	MW_RIGHT = (1U << 18),
123	MW_SELECT = (1U << 19),
124	MW_SET = (1U << 20),
125	MW_SYSTEM = (1U << 21),
126	MW_TOGGLE = (1U << 22),
127	MW_UP = (1U << 23),
128};
129
130const char *mixer_words =
131	"all        "
132	"balance    "
133	"capture    "
134	"card       "
135	"close      "
136	"control    "
137	"down       "
138	"focus      "
139	"help       "
140	"information"
141	"left       "
142	"mode       "
143	"mute       "
144	"next       "
145	"playback   "
146	"previous   "
147	"refresh    "
148	"right      "
149	"select     "
150	"set        "
151	"system     "
152	"toggle     "
153	"up         ";
154
155static unsigned int parse_words(const char *name, const char* wordlist, unsigned int itemlen, unsigned int *number) {
156	unsigned int words = 0;
157	unsigned int word;
158	int i;
159	char buf[16];
160	char *endptr;
161
162	while (*name) {
163		for (i = 0; i < (int)sizeof(buf) - 1; ++i) {
164			if (*name == '\0')
165				break;
166			if (*name == '_') {
167				++name;
168				break;
169			}
170			buf[i] = *name;
171			++name;
172		}
173		buf[i] = '\0';
174
175		if (buf[0] >= '0' && buf[0] <= '9') {
176			if (number) {
177				*number = strtoumax(buf, &endptr, 10);
178				if (*endptr != '\0')
179					return 0;
180			}
181			word = W_NUMBER;
182		}
183		else if ((i = strlist_index(wordlist, itemlen, buf)) >= 0)
184			word = i <= 30 ? (2U << i) : 0;
185		else
186			return 0;
187
188		if (words & word) // no duplicate words
189			return 0;
190		words |= word;
191	}
192
193	return words;
194}
195
196static int textbox_command_by_name(const char *name) {
197	switch (parse_words(name, textbox_words, 6, NULL)) {
198		case TW_TOP: return CMD_TEXTBOX_TOP;
199		case TW_BOTTOM: return CMD_TEXTBOX_BOTTOM;
200		case TW_CLOSE: return CMD_TEXTBOX_CLOSE;
201		case TW_UP: return CMD_TEXTBOX_UP;
202		case TW_DOWN: return CMD_TEXTBOX_DOWN;
203		case TW_LEFT: return CMD_TEXTBOX_LEFT;
204		case TW_RIGHT: return CMD_TEXTBOX_RIGHT;
205		case TW_PAGE|TW_UP: return CMD_TEXTBOX_PAGE_UP;
206		case TW_PAGE|TW_DOWN: return CMD_TEXTBOX_PAGE_DOWN;
207		case TW_PAGE|TW_LEFT: return CMD_TEXTBOX_PAGE_LEFT;
208		case TW_PAGE|TW_RIGHT: return CMD_TEXTBOX_PAGE_RIGHT;
209		default: return 0;
210	}
211}
212
213static int mixer_command_by_name(const char *name) {
214	unsigned int channel = 0;
215	unsigned int number = 1; // default numeric arg
216	unsigned int words = parse_words(name, mixer_words, 11, &number);
217
218	switch (words) {
219		case MW_HELP: return CMD_MIXER_HELP;
220		case MW_CLOSE: return CMD_MIXER_CLOSE;
221		case MW_REFRESH: return CMD_MIXER_REFRESH;
222		case MW_SELECT|MW_CARD: return CMD_MIXER_SELECT_CARD;
223		case MW_SYSTEM|MW_INFORMATION: return CMD_MIXER_SYSTEM_INFORMATION;
224		case MW_MODE|MW_ALL: return CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_ALL);
225		case MW_MODE|MW_CAPTURE: return CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_CAPTURE);
226		case MW_MODE|MW_PLAYBACK: return CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_PLAYBACK);
227		case MW_MODE|MW_TOGGLE: return CMD_MIXER_TOGGLE_VIEW_MODE;
228		case MW_CONTROL|MW_BALANCE: return CMD_MIXER_BALANCE_CONTROL;
229		case MW_NEXT:
230		case MW_NEXT|W_NUMBER:
231		case MW_PREVIOUS:
232		case MW_PREVIOUS|W_NUMBER:
233			return ((number < 1 || number > 511) ? 0 :
234					CMD_WITH_ARG((words & MW_NEXT
235							? CMD_MIXER_NEXT
236							: CMD_MIXER_PREVIOUS), number));
237		case MW_CONTROL|MW_FOCUS|W_NUMBER:
238			return ((number < 1 || number > 512) ? 0 :
239					CMD_WITH_ARG(CMD_MIXER_FOCUS_CONTROL, number - 1));
240	}
241
242	if (words & MW_LEFT)
243		channel |= LEFT;
244	if (words & MW_RIGHT)
245		channel |= RIGHT;
246	if (!channel)
247		channel = LEFT|RIGHT;
248
249	switch (words & ~(MW_LEFT|MW_RIGHT)) {
250		case MW_CONTROL|MW_UP:
251		case MW_CONTROL|MW_UP|W_NUMBER:
252		case MW_CONTROL|MW_DOWN:
253		case MW_CONTROL|MW_DOWN|W_NUMBER:
254			return ((number < 1 || number > 100) ? 0 :
255					CMD_WITH_ARG((words & MW_UP
256						 ? CMD_MIXER_CONTROL_UP_LEFT
257						 : CMD_MIXER_CONTROL_DOWN_LEFT) + channel - 1, number));
258		case MW_CONTROL|MW_SET|W_NUMBER:
259			return (number > 100 ? 0 :
260					CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT_LEFT + channel - 1, number));
261		case MW_TOGGLE|MW_MUTE:
262			return CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, channel);
263		case MW_TOGGLE|MW_CAPTURE:
264			return CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, channel);
265	}
266
267	return 0;
268}
269
270static int* element_by_name(const char *name) {
271	int idx = strlist_index(
272#ifdef TRICOLOR_VOLUME_BAR
273		"ctl_bar_hi        "
274#endif
275		"ctl_bar_lo        "
276#ifdef TRICOLOR_VOLUME_BAR
277		"ctl_bar_mi        "
278#endif
279		"ctl_capture       "
280		"ctl_frame         "
281		"ctl_inactive      "
282		"ctl_label         "
283		"ctl_label_focus   "
284		"ctl_label_inactive"
285		"ctl_mark_focus    "
286		"ctl_mute          "
287		"ctl_nocapture     "
288		"ctl_nomute        "
289		"errormsg          "
290		"infomsg           "
291		"menu              "
292		"menu_selected     "
293		"mixer_active      "
294		"mixer_frame       "
295		"mixer_text        "
296		"textbox           "
297		"textfield         ", 18, name);
298
299	if (idx < 0) {
300#ifndef TRICOLOR_VOLUME_BAR
301		if (strlist_index(
302			"ctl_bar_hi"
303			"ctl_bar_mi", 10, name) >= 0)
304			return &errno; // dummy element
305#endif
306		return NULL;
307	}
308
309	return &( ((int*) &attrs)[idx] );
310}
311
312static int cfg_bind(char **argv, unsigned int argc) {
313	const char *command_name;
314	command_enum command = 0;
315	unsigned int i;
316	int keys[3] = { -1, -1, -1 };
317	union {
318		command_enum *mixer_bindings;
319		uint8_t *textbox_bindings;
320	} bind_to = {
321		.mixer_bindings = mixer_bindings
322	};
323
324	if (argc == 2)
325		command_name = argv[1];
326	else if (argc == 3) {
327		command_name = argv[2];
328
329		if (! strcmp(argv[1], "textbox")) {
330			bind_to.textbox_bindings = textbox_bindings;
331		}
332		else if (! strcmp(argv[1], "mixer"))
333			; // bind_to.mixer_bindings = mixer_bindings
334		else {
335			error_message = _("invalid widget");
336			error_cause = argv[1];
337			return ERROR_CONFIG;
338		}
339	}
340	else {
341		return (argc < 2 ? ERROR_MISSING_ARGUMENTS : ERROR_TOO_MUCH_ARGUMENTS);
342	}
343
344	keys[0] = curskey_parse(argv[0]);
345	if (keys[0] < 0 || keys[0] >= (int)ARRAY_SIZE(mixer_bindings)) {
346		error_message = _("invalid key");
347		error_cause = argv[0];
348		return ERROR_CONFIG;
349	}
350
351	if (keys[0] == KEY_ENTER || keys[0] == '\n' || keys[0] == '\r') {
352		keys[0] = KEY_ENTER;
353		keys[1] = '\n';
354		keys[2] = '\r';
355	}
356
357	if (bind_to.textbox_bindings == textbox_bindings)
358		command = textbox_command_by_name(command_name);
359	else
360		command = mixer_command_by_name(command_name);
361
362	if (!command) {
363		if (!strcmp(command_name, "none"))
364			; // command = 0
365		else {
366			error_message = _("invalid command");
367			error_cause = command_name;
368			return ERROR_CONFIG;
369		}
370	}
371
372	for (i = 0; i < ARRAY_SIZE(keys) && keys[i] != -1; ++i) {
373		if (bind_to.textbox_bindings == textbox_bindings)
374			bind_to.textbox_bindings[keys[i]] = command;
375		else
376			bind_to.mixer_bindings[keys[i]] = command;
377	}
378
379	return 0;
380}
381
382static int cfg_color(char **argv, unsigned int argc)
383{
384	short fg_color, bg_color;
385	unsigned int i;
386	int *element;
387	int attr;
388
389	if (argc < 3)
390		return ERROR_MISSING_ARGUMENTS;
391
392	if (NULL == (element = element_by_name(argv[0]))) {
393		error_message = _("unknown theme element");
394		error_cause = argv[0];
395		return ERROR_CONFIG;
396	}
397
398	if (-2 == (fg_color = color_by_name(argv[1]))) {
399		error_message = _("unknown color");
400		error_cause = argv[1];
401		return ERROR_CONFIG;
402	}
403
404	if (-2 == (bg_color = color_by_name(argv[2]))) {
405		error_message = _("unknown color");
406		error_cause = argv[2];
407		return ERROR_CONFIG;
408	}
409
410	*element = get_color_pair(fg_color, bg_color);
411
412	for (i = 3; i < argc; ++i) {
413		if (-1 == (attr = attr_by_name(argv[i]))) {
414			error_message = _("unknown color attribute");
415			error_cause = argv[i];
416			return ERROR_CONFIG;
417		}
418		else
419			*element |= attr;
420	}
421	return 0;
422}
423
424static int cfg_set(char **argv, unsigned int argc)
425{
426	char *endptr;
427
428	if (argc == 2) {
429		if (! strcmp(argv[0], "mouse_wheel_step")) {
430			mouse_wheel_step = strtoumax(argv[1], &endptr, 10);
431			if (mouse_wheel_step > 100 || *endptr != '\0') {
432				mouse_wheel_step = 1;
433				error_message = _("invalid value");
434				error_cause = argv[1];
435				return ERROR_CONFIG;
436			}
437		}
438		else if (! strcmp(argv[0], "mouse_wheel_focuses_control")) {
439			if ((argv[1][0] == '0' || argv[1][0] == '1') && argv[1][1] == '\0')
440				mouse_wheel_focuses_control = argv[1][0] - '0';
441			else {
442				error_message = _("invalid value");
443				error_cause = argv[1];
444				return ERROR_CONFIG;
445			}
446		}
447		else if (!strcmp(argv[0], "background")) {
448			int bg_color = color_by_name(argv[1]);
449			if (bg_color == -2) {
450				error_message = _("unknown color");
451				error_cause = argv[1];
452				return ERROR_CONFIG;
453			}
454			reinit_colors(bg_color);
455		}
456		else {
457			error_message = _("unknown option");
458			error_cause = argv[0];
459			return ERROR_CONFIG;
460		}
461	}
462	else {
463		return (argc < 2 ? ERROR_MISSING_ARGUMENTS : ERROR_TOO_MUCH_ARGUMENTS);
464	}
465
466	return 0;
467}
468
469/* Split $line on whitespace, store it in $args, return the argument count.
470 * Return 0 for commented lines ('\s*#').
471 *
472 * This will modify contents of $line.
473 */
474static unsigned int parse_line(char *line, char **args, unsigned int args_size)
475{
476	unsigned int count;
477
478	for (count = 0; count < args_size; ++count) {
479		while (*line && isspace(*line))
480			++line;
481
482		if (*line == '\0')
483			break;
484
485		if (*line == '#' && count == 0)
486			break;
487
488		args[count] = line;
489
490		while (*line && !isspace(*line))
491			++line;
492
493		if (*line != '\0') {
494			*line = '\0';
495			++line;
496		}
497	}
498
499	return count;
500}
501
502static int process_line(char *line) {
503	char *args[16];
504	unsigned int argc = parse_line(line, args, ARRAY_SIZE(args));
505	int ret = 0;
506
507	if (argc >= 1) {
508		error_cause = NULL;
509		//error_message = _("unknown error");
510
511		if (argc >= ARRAY_SIZE(args))
512			ret = ERROR_TOO_MUCH_ARGUMENTS;
513		else {
514			ret = strlist_index(
515				"bind "
516				"color"
517				"set  ", 5, args[0]);
518			switch (ret) {
519				case 0: ret = cfg_bind(args + 1, argc - 1); break;
520				case 1: ret = cfg_color(args + 1, argc - 1); break;
521				case 2: ret = cfg_set(args + 1, argc - 1); break;
522				default: error_message = _("unknown command");
523			}
524		}
525
526		if (ret == ERROR_MISSING_ARGUMENTS)
527			error_message = _("missing arguments");
528		else if (ret == ERROR_TOO_MUCH_ARGUMENTS)
529			error_message = _("too much arguments");
530	}
531
532	return ret;
533}
534
535void parse_config_file(const char *file_name)
536{
537	char *buf;
538	unsigned int file_size;
539	unsigned int lineno;
540	unsigned int i;
541	char *line;
542
543	endwin(); // print warnings to stderr
544
545	buf = read_file(file_name, &file_size);
546	if (!buf) {
547		fprintf(stderr, "%s: %s\n", file_name, strerror(errno));
548		return;
549	}
550
551	curskey_init();
552	curskey_define_meta_keys(128);
553
554	lineno = 0;
555	line = buf;
556	for (i = 0; i < file_size; ++i) {
557		if (buf[i] == '\n') {
558			buf[i] = '\0';
559			++lineno;
560			if (process_line(line) < 0) {
561				if (error_cause)
562					fprintf(stderr, "%s:%d: %s: %s: %s\n", file_name, lineno, line, error_message, error_cause);
563				else
564					fprintf(stderr, "%s:%d: %s: %s\n", file_name, lineno, line, error_message);
565			}
566			line = &buf[i + 1];
567		}
568	}
569
570	free(buf);
571	curskey_destroy();
572}
573
574void parse_default_config_file() {
575	char file[4096];
576	const char *home;
577
578	home = getenv("XDG_CONFIG_HOME");
579	if (home && *home) {
580		snprintf(file, sizeof(file), "%s/alsamixer.rc", home);
581		if (! access(file, F_OK))
582			return parse_config_file(file);
583	}
584
585	home = getenv("HOME");
586	if (!home || !*home) {
587		struct passwd *pwd = getpwuid(getuid());
588		if (pwd)
589			home = pwd->pw_dir;
590	}
591
592	if (home && *home) {
593		snprintf(file, sizeof(file), "%s/.config/alsamixer.rc", home);
594		if (! access(file, F_OK))
595			return parse_config_file(file);
596
597		snprintf(file, sizeof(file), "%s/.alsamixer.rc", home);
598		if (! access(file, F_OK))
599			return parse_config_file(file);
600	}
601}
602