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