• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
21 static const char *error_message;
22 static const char *error_cause;
23 
strlist_index(const char * haystack,unsigned int itemlen,const char * needle)24 static 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 
color_by_name(const char * name)42 static 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 
attr_by_name(const char * name)55 static 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 
83 enum 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 
94 const char *textbox_words =
95 	"bottom"
96 	"close "
97 	"down  "
98 	"left  "
99 	"page  "
100 	"right "
101 	"top   "
102 	"up    ";
103 
104 enum 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 
130 const 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 
parse_words(const char * name,const char * wordlist,unsigned int itemlen,unsigned int * number)155 static 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 < 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 
textbox_command_by_name(const char * name)196 static 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 
mixer_command_by_name(const char * name)213 static 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 
element_by_name(const char * name)270 static 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 
cfg_bind(char ** argv,unsigned int argc)312 static 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] >= 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 
cfg_color(char ** argv,unsigned int argc)382 static 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 
cfg_set(char ** argv,unsigned int argc)424 static 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 			background_color = 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  */
parse_line(char * line,char ** args,unsigned int args_size)474 static 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 
process_line(char * line)502 static 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 
parse_config_file(const char * file_name)535 void 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 
parse_default_config_file()574 void 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