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 \
#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,
" -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"
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' },
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;
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();
--- /dev/null
+#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);
+ }
+}
--- /dev/null
+#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
--- /dev/null
+#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();
+}
--- /dev/null
+#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
{
}
-void initialize_curses(bool use_color)
+void initialize_curses(bool use_color, bool use_mouse)
{
curses_initialized = initscr();
cbreak();
#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);
}
#include CURSESINC
-void initialize_curses(bool use_color);
+void initialize_curses(bool use_color, bool use_mouse);
void mainloop(void);
void app_shutdown(void);
#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);
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;
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';
#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
}
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;
+}
+
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