]> git.alsa-project.org Git - alsa-utils.git/commitdiff
alsamixer: added configuration file parser
authorbraph <braph93@gmx.de>
Thu, 3 Oct 2019 17:06:11 +0000 (19:06 +0200)
committerJaroslav Kysela <perex@perex.cz>
Wed, 1 Jul 2020 14:10:35 +0000 (16:10 +0200)
Added configparser.c and curskey.c:
  - Lines starting with arbitrary whitespace + '#' are comments
  - Words in a command name don't have a fixed order (toggle_mute is the
      same as mute_toggle)

Moved read_file() from textbox.c to utils.c, so configparser.c can make
use of it.

Added command line options:
  -f/-F to specify/disable configuration file
  -m/-M to enable/disable mouse

Signed-off-by: Benjamin Abendroth <braph93@gmx.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
alsamixer/Makefile.am
alsamixer/cli.c
alsamixer/configparser.c [new file with mode: 0644]
alsamixer/configparser.h [new file with mode: 0644]
alsamixer/curskey.c [new file with mode: 0644]
alsamixer/curskey.h [new file with mode: 0644]
alsamixer/mainloop.c
alsamixer/mainloop.h
alsamixer/textbox.c
alsamixer/utils.c
alsamixer/utils.h

index d1021f9e5eb543abb673bdf98c090f528b2b3c02..14209d9970400a0516b1b57af6fbf7d5368b6d2e 100644 (file)
@@ -6,6 +6,8 @@ alsamixer_SOURCES = card_select.c card_select.h \
                bindings.c bindings.h \
                cli.c \
                colors.c colors.h \
+               curskey.c curskey.h \
+               configparser.c configparser.h \
                device_name.c device_name.h \
                die.c die.h \
                mainloop.c mainloop.h \
index 74683255a30a7faaaa8c602a3abd6d4793cde50a..23d34ad0d33b331cc25405f45a19c042a21643ba 100644 (file)
 #include "gettext_curses.h"
 #include "mixer_widget.h"
 #include "mainloop.h"
+#include "configparser.h"
 
 static int use_color = 1;
+static int use_mouse = 1;
+static const char* config_file = CONFIG_DEFAULT;
 static struct snd_mixer_selem_regopt selem_regopt = {
        .ver = 1,
        .abstract = SND_MIXER_SABSTRACT_NONE,
@@ -42,6 +45,10 @@ static void show_help(void)
               "  -h, --help              this help\n"
               "  -c, --card=NUMBER       sound card number or id\n"
               "  -D, --device=NAME       mixer device name\n"
+              "  -m, --mouse             enable mouse\n"
+              "  -M, --no-mouse          disable mouse\n"
+              "  -f, --config=FILE       configuration file\n"
+              "  -F, --no-config         do not load configuration file\n"
               "  -V, --view=MODE         starting view mode: playback/capture/all"));
        puts(_("Debugging options:\n"
               "  -g, --no-color          toggle using of colors\n"
@@ -50,11 +57,15 @@ static void show_help(void)
 
 static void parse_options(int argc, char *argv[])
 {
-       static const char short_options[] = "hc:D:V:gsa:";
+       static const char short_options[] = "hc:D:f:FmMV:gsa:";
        static const struct option long_options[] = {
                { .name = "help", .val = 'h' },
                { .name = "card", .has_arg = 1, .val = 'c' },
+               { .name = "config", .has_arg = 1, .val = 'f' },
+               { .name = "no-config", .val = 'F' },
                { .name = "device", .has_arg = 1, .val = 'D' },
+               { .name = "mouse", .val = 'm' },
+               { .name = "no-mouse", .val = 'M' },
                { .name = "view", .has_arg = 1, .val = 'V' },
                { .name = "no-color", .val = 'g' },
                { .name = "abstraction", .has_arg = 1, .val = 'a' },
@@ -83,6 +94,18 @@ static void parse_options(int argc, char *argv[])
                case 'D':
                        selem_regopt.device = optarg;
                        break;
+               case 'f':
+                       config_file = optarg;
+                       break;
+               case 'F':
+                       config_file = NULL;
+                       break;
+               case 'm':
+                       use_mouse = 1;
+                       break;
+               case 'M':
+                       use_mouse = 0;
+                       break;
                case 'V':
                        if (*optarg == 'p' || *optarg == 'P')
                                view_mode = VIEW_MODE_PLAYBACK;
@@ -127,7 +150,12 @@ int main(int argc, char *argv[])
 
        create_mixer_object(&selem_regopt);
 
-       initialize_curses(use_color);
+       initialize_curses(use_color, use_mouse);
+
+       if (config_file == CONFIG_DEFAULT)
+               parse_default_config_file();
+       else if (config_file)
+               parse_config_file(config_file);
 
        create_mixer_widget();
 
diff --git a/alsamixer/configparser.c b/alsamixer/configparser.c
new file mode 100644 (file)
index 0000000..93aa72a
--- /dev/null
@@ -0,0 +1,592 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <errno.h>
+#include <pwd.h>
+#include CURSESINC
+#include "colors.h"
+#include "gettext_curses.h"
+#include "utils.h"
+#include "curskey.h"
+#include "bindings.h"
+#include "mixer_widget.h"
+
+#define ERROR_CONFIG (-1)
+#define ERROR_MISSING_ARGUMENTS (-2)
+#define ERROR_TOO_MUCH_ARGUMENTS (-3)
+
+static const char *error_message;
+static const char *error_cause;
+
+static int strlist_index(const char *haystack, unsigned int itemlen, const char *needle) {
+       unsigned int needle_len;
+       unsigned int pos;
+       const char *found;
+
+       needle_len = strlen(needle);
+       if (needle_len <= itemlen && needle[needle_len - 1] != ' ') {
+               found = strstr(haystack, needle);
+               if (found) {
+                       pos = (found - haystack);
+                       if (pos % itemlen == 0 && (needle_len == itemlen || haystack[pos+needle_len] == ' '))
+                               return pos / itemlen;
+               }
+       }
+
+       return -1;
+}
+
+static int color_by_name(const char *name) {
+       return strlist_index(
+               "default"
+               "black  "
+               "red    "
+               "green  "
+               "yellow "
+               "blue   "
+               "magenta"
+               "cyan   "
+               "white  ", 7, name) - 1;
+};
+
+static int attr_by_name(const char *name) {
+       return (int[]) {
+               -1,
+               A_BOLD,
+               A_REVERSE,
+               A_STANDOUT,
+               A_DIM,
+               A_UNDERLINE,
+#ifdef A_ITALIC
+               A_ITALIC,
+#endif
+               A_NORMAL,
+               A_BLINK,
+       }[strlist_index(
+               "bold     "
+               "reverse  "
+               "standout "
+               "dim      "
+               "underline"
+#ifdef A_ITALIC
+               "italic   "
+#endif
+               "normal   "
+               "blink    ", 9, name) + 1];
+};
+
+#define W_NUMBER (1U << 0)
+
+enum textbox_word {
+       TW_BOTTOM = (1U << 1),
+       TW_CLOSE = (1U << 2),
+       TW_DOWN = (1U << 3),
+       TW_LEFT = (1U << 4),
+       TW_PAGE = (1U << 5),
+       TW_RIGHT = (1U << 6),
+       TW_TOP = (1U << 7),
+       TW_UP = (1U << 8),
+};
+
+const char *textbox_words =
+       "bottom"
+       "close "
+       "down  "
+       "left  "
+       "page  "
+       "right "
+       "top   "
+       "up    ";
+
+enum mixer_word {
+       MW_ALL = (1U << 1),
+       MW_BALANCE = (1U << 2),
+       MW_CAPTURE = (1U << 3),
+       MW_CARD = (1U << 4),
+       MW_CLOSE = (1U << 5),
+       MW_CONTROL = (1U << 6),
+       MW_DOWN = (1U << 7),
+       MW_FOCUS = (1U << 8),
+       MW_HELP = (1U << 9),
+       MW_INFORMATION = (1U << 10),
+       MW_LEFT = (1U << 11),
+       MW_MODE = (1U << 12),
+       MW_MUTE = (1U << 13),
+       MW_NEXT = (1U << 14),
+       MW_PLAYBACK = (1U << 15),
+       MW_PREVIOUS = (1U << 16),
+       MW_REFRESH = (1U << 17),
+       MW_RIGHT = (1U << 18),
+       MW_SELECT = (1U << 19),
+       MW_SET = (1U << 20),
+       MW_SYSTEM = (1U << 21),
+       MW_TOGGLE = (1U << 22),
+       MW_UP = (1U << 23),
+};
+
+const char *mixer_words =
+       "all        "
+       "balance    "
+       "capture    "
+       "card       "
+       "close      "
+       "control    "
+       "down       "
+       "focus      "
+       "help       "
+       "information"
+       "left       "
+       "mode       "
+       "mute       "
+       "next       "
+       "playback   "
+       "previous   "
+       "refresh    "
+       "right      "
+       "select     "
+       "set        "
+       "system     "
+       "toggle     "
+       "up         ";
+
+static unsigned int parse_words(const char *name, const char* wordlist, unsigned int itemlen, unsigned int *number) {
+       unsigned int words = 0;
+       unsigned int word;
+       unsigned int i;
+       char buf[16];
+       char *endptr;
+
+       while (*name) {
+               for (i = 0; i < sizeof(buf) - 1; ++i) {
+                       if (*name == '\0')
+                               break;
+                       if (*name == '_') {
+                               ++name;
+                               break;
+                       }
+                       buf[i] = *name;
+                       ++name;
+               }
+               buf[i] = '\0';
+
+               if (buf[0] >= '0' && buf[0] <= '9') {
+                       if (number) {
+                               *number = strtoumax(buf, &endptr, 10);
+                               if (*endptr != '\0')
+                                       return 0;
+                       }
+                       word = W_NUMBER;
+               }
+               else if ((i = strlist_index(wordlist, itemlen, buf)) >= 0)
+                       word = 2U << i;
+               else
+                       return 0;
+
+               if (words & word) // no duplicate words
+                       return 0;
+               words |= word;
+       }
+
+       return words;
+}
+
+static int textbox_command_by_name(const char *name) {
+       switch (parse_words(name, textbox_words, 6, NULL)) {
+               case TW_TOP: return CMD_TEXTBOX_TOP;
+               case TW_BOTTOM: return CMD_TEXTBOX_BOTTOM;
+               case TW_CLOSE: return CMD_TEXTBOX_CLOSE;
+               case TW_UP: return CMD_TEXTBOX_UP;
+               case TW_DOWN: return CMD_TEXTBOX_DOWN;
+               case TW_LEFT: return CMD_TEXTBOX_LEFT;
+               case TW_RIGHT: return CMD_TEXTBOX_RIGHT;
+               case TW_PAGE|TW_UP: return CMD_TEXTBOX_PAGE_UP;
+               case TW_PAGE|TW_DOWN: return CMD_TEXTBOX_PAGE_DOWN;
+               case TW_PAGE|TW_LEFT: return CMD_TEXTBOX_PAGE_LEFT;
+               case TW_PAGE|TW_RIGHT: return CMD_TEXTBOX_PAGE_RIGHT;
+               default: return 0;
+       }
+}
+
+static int mixer_command_by_name(const char *name) {
+       unsigned int channel = 0;
+       unsigned int number = 1; // default numeric arg
+       unsigned int words = parse_words(name, mixer_words, 11, &number);
+
+       switch (words) {
+               case MW_HELP: return CMD_MIXER_HELP;
+               case MW_CLOSE: return CMD_MIXER_CLOSE;
+               case MW_REFRESH: return CMD_MIXER_REFRESH;
+               case MW_SELECT|MW_CARD: return CMD_MIXER_SELECT_CARD;
+               case MW_SYSTEM|MW_INFORMATION: return CMD_MIXER_SYSTEM_INFORMATION;
+               case MW_MODE|MW_ALL: return CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_ALL);
+               case MW_MODE|MW_CAPTURE: return CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_CAPTURE);
+               case MW_MODE|MW_PLAYBACK: return CMD_WITH_ARG(CMD_MIXER_SET_VIEW_MODE, VIEW_MODE_PLAYBACK);
+               case MW_MODE|MW_TOGGLE: return CMD_MIXER_TOGGLE_VIEW_MODE;
+               case MW_CONTROL|MW_BALANCE: return CMD_MIXER_BALANCE_CONTROL;
+               case MW_NEXT:
+               case MW_NEXT|W_NUMBER:
+               case MW_PREVIOUS:
+               case MW_PREVIOUS|W_NUMBER:
+                       return ((number < 1 || number > 511) ? 0 :
+                                       CMD_WITH_ARG((words & MW_NEXT
+                                                       ? CMD_MIXER_NEXT
+                                                       : CMD_MIXER_PREVIOUS), number));
+               case MW_CONTROL|MW_FOCUS|W_NUMBER:
+                       return ((number < 1 || number > 512) ? 0 :
+                                       CMD_WITH_ARG(CMD_MIXER_FOCUS_CONTROL, number - 1));
+       }
+
+       if (words & MW_LEFT)
+               channel |= LEFT;
+       if (words & MW_RIGHT)
+               channel |= RIGHT;
+       if (!channel)
+               channel = LEFT|RIGHT;
+
+       switch (words & ~(MW_LEFT|MW_RIGHT)) {
+               case MW_CONTROL|MW_UP:
+               case MW_CONTROL|MW_UP|W_NUMBER:
+               case MW_CONTROL|MW_DOWN:
+               case MW_CONTROL|MW_DOWN|W_NUMBER:
+                       return ((number < 1 || number > 100) ? 0 :
+                                       CMD_WITH_ARG((words & MW_UP
+                                                ? CMD_MIXER_CONTROL_UP_LEFT
+                                                : CMD_MIXER_CONTROL_DOWN_LEFT) + channel - 1, number));
+               case MW_CONTROL|MW_SET|W_NUMBER:
+                       return (number > 100 ? 0 :
+                                       CMD_WITH_ARG(CMD_MIXER_CONTROL_SET_PERCENT_LEFT + channel - 1, number));
+               case MW_TOGGLE|MW_MUTE:
+                       return CMD_WITH_ARG(CMD_MIXER_TOGGLE_MUTE, channel);
+               case MW_TOGGLE|MW_CAPTURE:
+                       return CMD_WITH_ARG(CMD_MIXER_TOGGLE_CAPTURE, channel);
+       }
+
+       return 0;
+}
+
+static int* element_by_name(const char *name) {
+       int idx = strlist_index(
+#ifdef TRICOLOR_VOLUME_BAR
+               "ctl_bar_hi        "
+#endif
+               "ctl_bar_lo        "
+#ifdef TRICOLOR_VOLUME_BAR
+               "ctl_bar_mi        "
+#endif
+               "ctl_capture       "
+               "ctl_frame         "
+               "ctl_inactive      "
+               "ctl_label         "
+               "ctl_label_focus   "
+               "ctl_label_inactive"
+               "ctl_mark_focus    "
+               "ctl_mute          "
+               "ctl_nocapture     "
+               "ctl_nomute        "
+               "errormsg          "
+               "infomsg           "
+               "menu              "
+               "menu_selected     "
+               "mixer_active      "
+               "mixer_frame       "
+               "mixer_text        "
+               "textbox           "
+               "textfield         ", 18, name);
+
+       if (idx < 0) {
+#ifndef TRICOLOR_VOLUME_BAR
+               if (strlist_index(
+                       "ctl_bar_hi"
+                       "ctl_bar_mi", 10, name) >= 0)
+                       return &errno; // dummy element
+#endif
+               return NULL;
+       }
+
+       return &( ((int*) &attrs)[idx] );
+}
+
+static int cfg_bind(char **argv, unsigned int argc) {
+       const char *command_name;
+       command_enum command = 0;
+       unsigned int i;
+       int keys[3] = { -1, -1, -1 };
+       union {
+               command_enum *mixer_bindings;
+               uint8_t *textbox_bindings;
+       } bind_to = {
+               .mixer_bindings = mixer_bindings
+       };
+
+       if (argc == 2)
+               command_name = argv[1];
+       else if (argc == 3) {
+               command_name = argv[2];
+
+               if (! strcmp(argv[1], "textbox")) {
+                       bind_to.textbox_bindings = textbox_bindings;
+               }
+               else if (! strcmp(argv[1], "mixer"))
+                       ; // bind_to.mixer_bindings = mixer_bindings
+               else {
+                       error_message = _("invalid widget");
+                       error_cause = argv[1];
+                       return ERROR_CONFIG;
+               }
+       }
+       else {
+               return (argc < 2 ? ERROR_MISSING_ARGUMENTS : ERROR_TOO_MUCH_ARGUMENTS);
+       }
+
+       keys[0] = curskey_parse(argv[0]);
+       if (keys[0] < 0 || keys[0] >= ARRAY_SIZE(mixer_bindings)) {
+               error_message = _("invalid key");
+               error_cause = argv[0];
+               return ERROR_CONFIG;
+       }
+
+       if (keys[0] == KEY_ENTER || keys[0] == '\n' || keys[0] == '\r') {
+               keys[0] = KEY_ENTER;
+               keys[1] = '\n';
+               keys[2] = '\r';
+       }
+
+       if (bind_to.textbox_bindings == textbox_bindings)
+               command = textbox_command_by_name(command_name);
+       else
+               command = mixer_command_by_name(command_name);
+
+       if (!command) {
+               if (!strcmp(command_name, "none"))
+                       ; // command = 0
+               else {
+                       error_message = _("invalid command");
+                       error_cause = command_name;
+                       return ERROR_CONFIG;
+               }
+       }
+
+       for (i = 0; i < ARRAY_SIZE(keys) && keys[i] != -1; ++i) {
+               if (bind_to.textbox_bindings == textbox_bindings)
+                       bind_to.textbox_bindings[keys[i]] = command;
+               else
+                       bind_to.mixer_bindings[keys[i]] = command;
+       }
+
+       return 0;
+}
+
+static int cfg_color(char **argv, unsigned int argc)
+{
+       short fg_color, bg_color;
+       unsigned int i;
+       int *element;
+       int attr;
+
+       if (argc < 3)
+               return ERROR_MISSING_ARGUMENTS;
+
+       if (NULL == (element = element_by_name(argv[0]))) {
+               error_message = _("unknown theme element");
+               error_cause = argv[0];
+               return ERROR_CONFIG;
+       }
+
+       if (-2 == (fg_color = color_by_name(argv[1]))) {
+               error_message = _("unknown color");
+               error_cause = argv[1];
+               return ERROR_CONFIG;
+       }
+
+       if (-2 == (bg_color = color_by_name(argv[2]))) {
+               error_message = _("unknown color");
+               error_cause = argv[2];
+               return ERROR_CONFIG;
+       }
+
+       *element = get_color_pair(fg_color, bg_color);
+
+       for (i = 3; i < argc; ++i) {
+               if (-1 == (attr = attr_by_name(argv[i]))) {
+                       error_message = _("unknown color attribute");
+                       error_cause = argv[i];
+                       return ERROR_CONFIG;
+               }
+               else
+                       *element |= attr;
+       }
+       return 0;
+}
+
+static int cfg_set(char **argv, unsigned int argc)
+{
+       char *endptr;
+
+       if (argc == 2) {
+               if (! strcmp(argv[0], "mouse_wheel_step")) {
+                       mouse_wheel_step = strtoumax(argv[1], &endptr, 10);
+                       if (mouse_wheel_step > 100 || *endptr != '\0') {
+                               mouse_wheel_step = 1;
+                               error_message = _("invalid value");
+                               error_cause = argv[1];
+                               return ERROR_CONFIG;
+                       }
+               }
+               else if (! strcmp(argv[0], "mouse_wheel_focuses_control")) {
+                       if ((argv[1][0] == '0' || argv[1][0] == '1') && argv[1][1] == '\0')
+                               mouse_wheel_focuses_control = argv[1][0] - '0';
+                       else {
+                               error_message = _("invalid value");
+                               error_cause = argv[1];
+                               return ERROR_CONFIG;
+                       }
+               }
+               else {
+                       error_message = _("unknown option");
+                       error_cause = argv[0];
+                       return ERROR_CONFIG;
+               }
+       }
+       else {
+               return (argc < 2 ? ERROR_MISSING_ARGUMENTS : ERROR_TOO_MUCH_ARGUMENTS);
+       }
+
+       return 0;
+}
+
+/* Split $line on whitespace, store it in $args, return the argument count.
+ * Return 0 for commented lines ('\s*#').
+ *
+ * This will modify contents of $line.
+ */
+static unsigned int parse_line(char *line, char **args, unsigned int args_size)
+{
+       unsigned int count;
+
+       for (count = 0; count < args_size; ++count) {
+               while (*line && isspace(*line))
+                       ++line;
+
+               if (*line == '\0')
+                       break;
+
+               if (*line == '#' && count == 0)
+                       break;
+
+               args[count] = line;
+
+               while (*line && !isspace(*line))
+                       ++line;
+
+               if (*line != '\0') {
+                       *line = '\0';
+                       ++line;
+               }
+       }
+
+       return count;
+}
+
+static int process_line(char *line) {
+       char *args[16];
+       unsigned int argc = parse_line(line, args, ARRAY_SIZE(args));
+       int ret = 0;
+
+       if (argc >= 1) {
+               error_cause = NULL;
+               //error_message = _("unknown error");
+
+               if (argc >= ARRAY_SIZE(args))
+                       ret = ERROR_TOO_MUCH_ARGUMENTS;
+               else {
+                       ret = strlist_index(
+                               "bind "
+                               "color"
+                               "set  ", 5, args[0]);
+                       switch (ret) {
+                               case 0: ret = cfg_bind(args + 1, argc - 1); break;
+                               case 1: ret = cfg_color(args + 1, argc - 1); break;
+                               case 2: ret = cfg_set(args + 1, argc - 1); break;
+                               default: error_message = _("unknown command");
+                       }
+               }
+
+               if (ret == ERROR_MISSING_ARGUMENTS)
+                       error_message = _("missing arguments");
+               else if (ret == ERROR_TOO_MUCH_ARGUMENTS)
+                       error_message = _("too much arguments");
+       }
+
+       return ret;
+}
+
+void parse_config_file(const char *file_name)
+{
+       char *buf;
+       unsigned int file_size;
+       unsigned int lineno;
+       unsigned int i;
+       char *line;
+
+       endwin(); // print warnings to stderr
+
+       buf = read_file(file_name, &file_size);
+       if (!buf) {
+               fprintf(stderr, "%s: %s\n", file_name, strerror(errno));
+               return;
+       }
+
+       curskey_init();
+       curskey_define_meta_keys(128);
+
+       lineno = 0;
+       line = buf;
+       for (i = 0; i < file_size; ++i) {
+               if (buf[i] == '\n') {
+                       buf[i] = '\0';
+                       ++lineno;
+                       if (process_line(line) < 0) {
+                               if (error_cause)
+                                       fprintf(stderr, "%s:%d: %s: %s: %s\n", file_name, lineno, line, error_message, error_cause);
+                               else
+                                       fprintf(stderr, "%s:%d: %s: %s\n", file_name, lineno, line, error_message);
+                       }
+                       line = &buf[i + 1];
+               }
+       }
+
+       free(buf);
+       curskey_destroy();
+}
+
+void parse_default_config_file() {
+       char file[4096];
+       const char *home;
+
+       home = getenv("XDG_CONFIG_HOME");
+       if (home && *home) {
+               snprintf(file, sizeof(file), "%s/alsamixer.rc", home);
+               if (! access(file, F_OK))
+                       return parse_config_file(file);
+       }
+
+       home = getenv("HOME");
+       if (!home || !*home) {
+               struct passwd *pwd = getpwuid(getuid());
+               if (pwd)
+                       home = pwd->pw_dir;
+       }
+
+       if (home && *home) {
+               snprintf(file, sizeof(file), "%s/.config/alsamixer.rc", home);
+               if (! access(file, F_OK))
+                       return parse_config_file(file);
+
+               snprintf(file, sizeof(file), "%s/.alsamixer.rc", home);
+               if (! access(file, F_OK))
+                       return parse_config_file(file);
+       }
+}
diff --git a/alsamixer/configparser.h b/alsamixer/configparser.h
new file mode 100644 (file)
index 0000000..43f5392
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef CONFIGPARSER_H_INCLUDED
+#define CONFIGPARSER_H_INCLUDED
+
+#define CONFIG_DEFAULT ((const char*) 1)
+
+void parse_config_file(const char *file);
+void parse_default_config_file();
+
+#endif
diff --git a/alsamixer/curskey.c b/alsamixer/curskey.c
new file mode 100644 (file)
index 0000000..5f39b4a
--- /dev/null
@@ -0,0 +1,274 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include "curskey.h"
+#include "utils.h"
+#include "mem.h"
+
+struct curskey_key {
+       char *keyname;
+       int keycode;
+};
+
+static struct curskey_key *keynames;
+static unsigned int keynames_count;
+
+unsigned int meta_keycode_start;
+static uint64_t invalid_meta_char_mask[2];
+
+// Names for non-printable/whitespace characters and aliases for existing keys
+static const struct curskey_key keyname_aliases[] = {
+       // Sorted by `keyname`
+       { "DEL",      KEY_DEL },
+       { "DELETE",   KEY_DC },
+       { "ENTER",    '\n' },
+       { "ENTER",    '\r' },
+       { "ESCAPE",   KEY_ESCAPE },
+       { "INSERT",   KEY_IC },
+       { "PAGEDOWN", KEY_NPAGE },
+       { "PAGEUP",   KEY_PPAGE },
+       { "SPACE",    KEY_SPACE },
+       { "TAB",      KEY_TAB }
+};
+
+#define STARTSWITH_KEY(S) \
+       ((name[0] == 'K' || name[0] == 'k') && \
+       (name[1] == 'E' || name[1] == 'e') && \
+       (name[2] == 'Y' || name[2] == 'y') && \
+       (name[3] == '_'))
+
+#define IS_META(S) \
+       ((S[0] == 'M' || S[0] == 'm' || S[0] == 'A' || S[0] == 'a') && S[1] == '-')
+
+static int curskey_key_cmp(const void *a, const void *b) {
+       return strcmp(((struct curskey_key*) a)->keyname,
+                       ((struct curskey_key*) b)->keyname);
+}
+
+static int curskey_find(const struct curskey_key *table, unsigned int size, const char *name) {
+       unsigned int start = 0;
+       unsigned int end = size;
+       unsigned int i;
+       int cmp;
+
+       while (1) {
+               i = (start+end) / 2;
+               cmp = strcasecmp(name, table[i].keyname);
+
+               if (cmp == 0)
+                       return table[i].keycode;
+               else if (end == start + 1)
+                       return ERR;
+               else if (cmp > 0)
+                       start = i;
+               else
+                       end = i;
+       }
+}
+
+/* Translate the name of a ncurses KEY_ constant to its value.
+ *     "KEY_DOWN" -> 258
+ *
+ * Return ERR on failure.
+ */
+int curskey_keycode(const char *name)
+{
+       int i;
+
+       if (! name)
+               return ERR;
+
+       if (STARTSWITH_KEY(name))
+               name += 4;
+
+       if (name[0] == 'F' || name[0] == 'f') {
+               i = (name[1] == '(' ? 2 : 1);
+
+               if (name[i] >= '0' && name[i] <= '9') {
+                       i = atoi(name + i);
+                       if (i >= 1 && i <= 63)
+                               return KEY_F(i);
+               }
+       }
+
+       i = curskey_find(keyname_aliases, ARRAY_SIZE(keyname_aliases), name);
+       if (i != ERR)
+               return i;
+
+       return curskey_find(keynames, keynames_count, name);
+}
+
+static void free_ncurses_keynames() {
+       if (keynames) {
+               while (keynames_count)
+                       free(keynames[--keynames_count].keyname);
+               free(keynames);
+               keynames = NULL;
+       }
+}
+
+/* Create the list of ncurses KEY_ constants and their names.
+ * Returns OK on success, ERR on failure.
+ */
+int create_ncurses_keynames() {
+       int     key;
+       char *name;
+
+       free_ncurses_keynames();
+       keynames = ccalloc(sizeof(struct curskey_key), (KEY_MAX - KEY_MIN));
+
+       for (key = KEY_MIN; key != KEY_MAX; ++key) {
+               name = (char*) keyname(key);
+
+               if (!name || !STARTSWITH_KEY(name))
+                       continue;
+
+               name += 4;
+               if (name[0] == 'F' && name[1] == '(')
+                       continue; // ignore KEY_F(1),...
+
+               keynames[keynames_count].keycode = key;
+               keynames[keynames_count].keyname = cstrdup(name);
+               ++keynames_count;
+       }
+
+       keynames = crealloc(keynames, keynames_count * sizeof(struct curskey_key));
+       qsort(keynames, keynames_count, sizeof(struct curskey_key), curskey_key_cmp);
+
+       return OK;
+}
+
+/* Defines meta escape sequences in ncurses.
+ *
+ * Some combinations with meta/alt may not be available since they collide
+ * with the prefix of a pre-defined key.
+ * For example, keys F1 - F4 begin with "\eO", so ALT-O cannot be defined.
+ *
+ * Returns OK if meta keys are available, ERR otherwise.
+ */
+int curskey_define_meta_keys(unsigned int keycode_start) {
+#ifdef NCURSES_VERSION
+       int ch;
+       int keycode;
+       int new_keycode = keycode_start;
+       char key_sequence[3] = "\e ";
+
+       invalid_meta_char_mask[0] = 0;
+       invalid_meta_char_mask[1] = 0;
+
+       for (ch = 0; ch <= CURSKEY_MAX_META_CHAR; ++ch) {
+               key_sequence[1] = ch;
+               keycode = key_defined(key_sequence);
+               if (! keycode) {
+                       define_key(key_sequence, new_keycode);
+               }
+               else if (keycode == new_keycode)
+                       ;
+               else
+                       invalid_meta_char_mask[ch/65] |= (1UL << (ch % 64));
+
+               ++new_keycode;
+       }
+
+       meta_keycode_start = keycode_start;
+       return OK;
+#endif
+       return ERR;
+}
+
+/* Return the keycode for a key with modifiers applied.
+ *
+ * Available modifiers are:
+ *     - CURSKEY_MOD_META / CURSKEY_MOD_ALT
+ *     - CURSKEY_MOD_CNTRL
+ *
+ * See also the macros curskey_meta_key(), curskey_cntrl_key().
+ *
+ * Returns ERR if the modifiers cannot be applied to this key.
+ */
+int curskey_mod_key(int key, unsigned int modifiers) {
+       if (modifiers & CURSKEY_MOD_CNTRL) {
+               if ((key >= 'A' && key <= '_') || (key >= 'a' && key <= 'z') || key == ' ')
+                       key = key % 32;
+               else
+                       return ERR;
+       }
+
+       if (modifiers & CURSKEY_MOD_META) {
+               if (meta_keycode_start &&
+                               (key >= 0 && key <= CURSKEY_MAX_META_CHAR) &&
+                               ! (invalid_meta_char_mask[key/65] & (1UL << (key % 64)))) {
+                       key = meta_keycode_start + key;
+               }
+               else
+                       return ERR;
+       }
+
+       return key;
+}
+
+/* Return the ncurses keycode for a key definition.
+ *
+ * Key definition may be:
+ *     - Single character (a, z, ...)
+ *     - Character with control-modifier (^x, C-x, c-x, ...)
+ *     - Character with meta/alt-modifier (M-x, m-x, A-x, a-x, ...)
+ *     - Character with both modifiers (C-M-x, M-C-x, M-^x, ...)
+ *     - Curses keyname, no modifiers allowed (KEY_HOME, HOME, F1, F(1), ...)
+ *
+ * Returns ERR if either
+ *     - The key definition is NULL or empty
+ *     - The key could not be found ("KEY_FOO")
+ *     - The key combination is invalid in general ("C-TAB", "C-RETURN")
+ *     - The key is invalid because of compile time options (the
+ *             `define_key()` function was not available.)
+ *     - The key is invalid because it could not be defined by
+ *             curskey_define_meta_keys()
+ */
+int curskey_parse(const char *def) {
+       int c;
+       unsigned int mod = 0;
+
+       if (! def)
+               return ERR;
+
+       for (;;) {
+               if (def[0] == '^' && def[1] != '\0') {
+                       ++def;
+                       mod |= CURSKEY_MOD_CNTRL;
+               }
+               else if ((def[0] == 'C' || def[0] == 'c') && def[1] == '-') {
+                       def += 2;
+                       mod |= CURSKEY_MOD_CNTRL;
+               }
+               else if (IS_META(def)) {
+                       if (! meta_keycode_start)
+                               return ERR;
+                       def += 2;
+                       mod |= CURSKEY_MOD_ALT;
+               }
+               else
+                       break;
+       }
+
+       if (*def == '\0')
+               return ERR;
+       else if (*(def+1) == '\0')
+               c = *def;
+       else
+               c = curskey_keycode(def);
+
+       return curskey_mod_key(c, mod);
+}
+
+/* Initialize curskey.
+ * Returns OK on success, ERR on failure.  */
+int curskey_init() {
+       keypad(stdscr, TRUE);
+       return create_ncurses_keynames();
+}
+
+/* Destroy curskey.  */
+void curskey_destroy() {
+       free_ncurses_keynames();
+}
diff --git a/alsamixer/curskey.h b/alsamixer/curskey.h
new file mode 100644 (file)
index 0000000..129a652
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef CURSKEY_H_INCLUDED
+#define CURSKEY_H_INCLUDED
+
+#include CURSESINC
+
+/* Additional KEY_ constants */
+#define KEY_SPACE ' '
+#define KEY_TAB '\t'
+#define KEY_DEL 127
+#define KEY_ESCAPE 27
+#define KEY_INSERT KEY_IC
+#define KEY_DELETE KEY_DC
+#define KEY_PAGEUP KEY_PPAGE
+#define KEY_PAGEDOWN KEY_NPAGE
+
+/* Modifiers */
+#define CURSKEY_MOD_CNTRL      1U
+#define CURSKEY_MOD_META       2U
+#define CURSKEY_MOD_ALT                CURSKEY_MOD_META
+
+/* Defines the range of characters which should be "meta-able" */
+#define CURSKEY_MAX_META_CHAR 127
+
+int curskey_init();
+void curskey_destroy();
+int curskey_define_meta_keys(unsigned int keycode_start);
+
+int curskey_parse(const char *keydef);
+int curskey_mod_key(int key, unsigned int modifiers);
+
+#define curskey_meta_key(KEY) \
+       curskey_mod_key(KEY, CURSKEY_MOD_META)
+
+#define curskey_cntrl_key(KEY) \
+       curskey_mod_key(KEY, CURSKEY_MOD_CNTRL)
+
+#endif
index e80f23275b8ac208cb77688d6f739700253725cc..e67c0f821e8a0c8ca55719be45c5da0dc1fbbcb4 100644 (file)
@@ -40,7 +40,7 @@ static void black_hole_error_handler(const char *file, int line,
 {
 }
 
-void initialize_curses(bool use_color)
+void initialize_curses(bool use_color, bool use_mouse)
 {
        curses_initialized = initscr();
        cbreak();
@@ -50,7 +50,8 @@ void initialize_curses(bool use_color)
 #endif
        window_size_changed(); /* update screen_lines/cols */
        init_colors(use_color);
-       mousemask(ALL_MOUSE_EVENTS, NULL);
+       if (use_mouse)
+               mousemask(ALL_MOUSE_EVENTS, NULL);
        snd_lib_error_set_handler(black_hole_error_handler);
 }
 
index 22317be2ff6734b1a0bd0e30072a654b9029a839..d777e0f4ca71f344c56624b1e01f8af01a198d09 100644 (file)
@@ -3,7 +3,7 @@
 
 #include CURSESINC
 
-void initialize_curses(bool use_color);
+void initialize_curses(bool use_color, bool use_mouse);
 void mainloop(void);
 void app_shutdown(void);
 
index 5c39b794468a8247f531aa8df205e7d6da5922e9..d85df0e39332fa2519a8acfdb538a1380c51ec6e 100644 (file)
@@ -34,8 +34,6 @@
 #include "textbox.h"
 #include "bindings.h"
 
-#define MAX_FILE_SIZE 1048576
-
 static void create_text_box(const char *const *lines, unsigned int count,
                            const char *title, int attrs);
 
@@ -67,38 +65,6 @@ void show_alsa_error(const char *msg, int err)
        create_text_box(lines, count, _("Error"), attrs.errormsg);
 }
 
-static char *read_file(const char *file_name, unsigned int *file_size)
-{
-       FILE *f;
-       int err;
-       char *buf;
-       unsigned int allocated = 2048;
-       unsigned int bytes_read;
-
-       f = fopen(file_name, "r");
-       if (!f) {
-               err = errno;
-               buf = casprintf(_("Cannot open file \"%s\"."), file_name);
-               show_error(buf, err);
-               free(buf);
-               return NULL;
-       }
-       *file_size = 0;
-       buf = NULL;
-       do {
-               allocated *= 2;
-               buf = crealloc(buf, allocated);
-               bytes_read = fread(buf + *file_size, 1, allocated - *file_size, f);
-               *file_size += bytes_read;
-       } while (*file_size == allocated && allocated < MAX_FILE_SIZE);
-       fclose(f);
-       if (*file_size > 0 && buf[*file_size - 1] != '\n' && *file_size < allocated) {
-               buf[*file_size] = '\n';
-               ++*file_size;
-       }
-       return buf;
-}
-
 void show_textfile(const char *file_name)
 {
        char *buf;
@@ -109,8 +75,12 @@ void show_textfile(const char *file_name)
        const char *start_line;
 
        buf = read_file(file_name, &file_size);
-       if (!buf)
+       if (!buf) {
+               int err = errno;
+               buf = casprintf(_("Cannot open file \"%s\"."), file_name);
+               show_error(buf, err);
                return;
+       }
        line_count = 0;
        for (i = 0; i < file_size; ++i)
                line_count += buf[i] == '\n';
index 7c69b9c28fde4f20d7ea6794fd78260604f19e76..9ee2e32713618a03e78d7fe5d3a8d2e3963cb22c 100644 (file)
 #include <stdlib.h>
 #include <string.h>
 #include <wchar.h>
+#include <errno.h>
+#include <stdio.h>
 #include "utils.h"
+#include "mem.h"
 
 /*
  * mbs_at_width - compute screen position in a string
@@ -111,3 +114,36 @@ unsigned int get_max_mbs_width(const char *const *s, unsigned int count)
        }
        return max_width;
 }
+
+#define MAX_FILE_SIZE 1048576
+char *read_file(const char *file_name, unsigned int *file_size)
+{
+       FILE *f;
+       int err;
+       char *buf;
+       unsigned int allocated = 2048;
+       unsigned int bytes_read;
+
+       f = fopen(file_name, "r");
+       if (!f) {
+               err = errno;
+               free(buf);
+               errno = err;
+               return NULL;
+       }
+       *file_size = 0;
+       buf = NULL;
+       do {
+               allocated *= 2;
+               buf = crealloc(buf, allocated);
+               bytes_read = fread(buf + *file_size, 1, allocated - *file_size, f);
+               *file_size += bytes_read;
+       } while (*file_size == allocated && allocated < MAX_FILE_SIZE);
+       fclose(f);
+       if (*file_size > 0 && buf[*file_size - 1] != '\n' && *file_size < allocated) {
+               buf[*file_size] = '\n';
+               ++*file_size;
+       }
+       return buf;
+}
+
index 00a52ddc08860392cc55e12e3027ae9a901b4d7a..8301311bf04de94e754241b38c1c2ddb53034adb 100644 (file)
@@ -6,5 +6,6 @@
 unsigned int get_mbs_width(const char *s);
 unsigned int get_max_mbs_width(const char *const *s, unsigned int count);
 const char *mbs_at_width(const char *s, int *width, int dir);
+char *read_file(const char *file_name, unsigned int *file_size);
 
 #endif