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