From 89e746368c5a9019b86647bb12e442f5c05b0279 Mon Sep 17 00:00:00 2001 From: braph Date: Thu, 3 Oct 2019 19:06:11 +0200 Subject: [PATCH] alsamixer: added configuration file parser 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 Signed-off-by: Jaroslav Kysela --- alsamixer/Makefile.am | 2 + alsamixer/cli.c | 32 ++- alsamixer/configparser.c | 592 +++++++++++++++++++++++++++++++++++++++ alsamixer/configparser.h | 9 + alsamixer/curskey.c | 274 ++++++++++++++++++ alsamixer/curskey.h | 37 +++ alsamixer/mainloop.c | 5 +- alsamixer/mainloop.h | 2 +- alsamixer/textbox.c | 40 +-- alsamixer/utils.c | 36 +++ alsamixer/utils.h | 1 + 11 files changed, 990 insertions(+), 40 deletions(-) create mode 100644 alsamixer/configparser.c create mode 100644 alsamixer/configparser.h create mode 100644 alsamixer/curskey.c create mode 100644 alsamixer/curskey.h diff --git a/alsamixer/Makefile.am b/alsamixer/Makefile.am index d1021f9..14209d9 100644 --- a/alsamixer/Makefile.am +++ b/alsamixer/Makefile.am @@ -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 \ diff --git a/alsamixer/cli.c b/alsamixer/cli.c index 7468325..23d34ad 100644 --- a/alsamixer/cli.c +++ b/alsamixer/cli.c @@ -27,8 +27,11 @@ #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 index 0000000..93aa72a --- /dev/null +++ b/alsamixer/configparser.c @@ -0,0 +1,592 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#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 index 0000000..43f5392 --- /dev/null +++ b/alsamixer/configparser.h @@ -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 index 0000000..5f39b4a --- /dev/null +++ b/alsamixer/curskey.c @@ -0,0 +1,274 @@ +#include +#include +#include +#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 index 0000000..129a652 --- /dev/null +++ b/alsamixer/curskey.h @@ -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 diff --git a/alsamixer/mainloop.c b/alsamixer/mainloop.c index e80f232..e67c0f8 100644 --- a/alsamixer/mainloop.c +++ b/alsamixer/mainloop.c @@ -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); } diff --git a/alsamixer/mainloop.h b/alsamixer/mainloop.h index 22317be..d777e0f 100644 --- a/alsamixer/mainloop.h +++ b/alsamixer/mainloop.h @@ -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); diff --git a/alsamixer/textbox.c b/alsamixer/textbox.c index 5c39b79..d85df0e 100644 --- a/alsamixer/textbox.c +++ b/alsamixer/textbox.c @@ -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'; diff --git a/alsamixer/utils.c b/alsamixer/utils.c index 7c69b9c..9ee2e32 100644 --- a/alsamixer/utils.c +++ b/alsamixer/utils.c @@ -24,7 +24,10 @@ #include #include #include +#include +#include #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; +} + diff --git a/alsamixer/utils.h b/alsamixer/utils.h index 00a52dd..8301311 100644 --- a/alsamixer/utils.h +++ b/alsamixer/utils.h @@ -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 -- 2.47.1