diff --git a/doc/man/mc.1.in b/doc/man/mc.1.in index c57c3f22ca..c547152ab3 100644 --- a/doc/man/mc.1.in +++ b/doc/man/mc.1.in @@ -53,12 +53,19 @@ Midnight Commander. Force a "normal tracking" mouse mode. Used when running on xterm\-capable terminals (tmux/screen). .TP +.I \-k file, \-\-keydef=file +Specify a name of keydef file in the command line. +.TP .I \-K file, \-\-keymap=file Specify a name of keymap file in the command line. .TP .I \-l file, \-\-ftplog=file Save the ftpfs dialog with the server in file. .TP +.I \-\-nokeydef +Don't load key definitions from any file, use hardcoded and terminfo +escape sequences. +.TP .I \-\-nokeymap Don't load key bindings from any file, use default hardcoded keys. .TP @@ -3783,11 +3790,11 @@ to save and restore position in opened files. .SH "Terminal databases" Midnight Commander provides a way to fix your system terminal database without requiring root privileges. Midnight Commander -searches in the system initialization file (the mc.lib file located in +searches in the mc.keydef file located in Midnight Commander's library directory) and in the -~/.config/mc/ini file for the section -"terminal:your\-terminal\-name" and then for the section -"terminal:general", each line of the section contains a key symbol that +~/.config/mc/mc.keydef file for the section +"your\-terminal\-name" and then for the section +"_common_", each line of the section contains a key symbol that you want to define, followed by an equal sign and the definition for the key. You can use the special \ee form to represent the escape character and the ^x to represent the control\-x character. diff --git a/lib/fileloc.h b/lib/fileloc.h index 3e3fb34f64..2f52a27cc7 100644 --- a/lib/fileloc.h +++ b/lib/fileloc.h @@ -26,6 +26,7 @@ #define MC_LOCAL_MENU ".mc.menu" #define MC_HINT "hints" PATH_SEP_STR "mc.hint" #define MC_HELP "help" PATH_SEP_STR "mc.hlp" +#define GLOBAL_KEYDEF_FILE "mc.keydef" #define GLOBAL_KEYMAP_FILE "mc.keymap" #define CHARSETS_LIST "mc.charsets" #define MC_MACRO_FILE "mc.macros" diff --git a/lib/mcconfig.h b/lib/mcconfig.h index e73f166c97..fac114cbc6 100644 --- a/lib/mcconfig.h +++ b/lib/mcconfig.h @@ -64,6 +64,8 @@ gboolean *mc_config_get_bool_list (mc_config_t *mc_config, const gchar *group, c gsize *length); int *mc_config_get_int_list (mc_config_t *mc_config, const gchar *group, const gchar *param, gsize *length); +GPtrArray *mc_config_get_escape_sequence_list (mc_config_t *mc_config, const gchar *group, + const gchar *param); /* mcconfig/set.c: */ @@ -83,6 +85,8 @@ void mc_config_set_bool_list (mc_config_t *mc_config, const gchar *group, const gboolean value[], gsize length); void mc_config_set_int_list (mc_config_t *mc_config, const gchar *group, const gchar *param, int value[], gsize length); +void mc_config_set_escape_sequence_list (mc_config_t *mc_config, const gchar *group, + const gchar *param, const GPtrArray *value); /* mcconfig/paths.c: */ @@ -96,6 +100,9 @@ const char *mc_config_get_path (void); char *mc_config_get_full_path (const char *config_name); vfs_path_t *mc_config_get_full_vpath (const char *config_name); +char *mc_config_get_full_config_name (const char *subdir, const char *config_file_name, + const char *suffix); + /* mcconfig/history.h */ /* read history to the mc_config, but don't save config to file */ diff --git a/lib/mcconfig/get.c b/lib/mcconfig/get.c index 7e8081a6c2..bbc12e6d6a 100644 --- a/lib/mcconfig/get.c +++ b/lib/mcconfig/get.c @@ -24,6 +24,7 @@ #include "lib/global.h" #include "lib/strutil.h" +#include "lib/terminal.h" // encode_controls(), decode_controls() #include "lib/mcconfig.h" @@ -254,3 +255,50 @@ mc_config_get_int_list (mc_config_t *mc_config, const gchar *group, const gchar } /* --------------------------------------------------------------------------------------------- */ + +/* + * An interface matching the other mc_config_get_*_list methods. + * + * Gets space-separated values, each parsed according to decode_controls(). + * + * Primarily useful for having escape sequences in easy-to-read format, including \e for the escape + * character without having to double-escape it, and without having to escape semicolons. + * + * @return array of GString + */ +GPtrArray * +mc_config_get_escape_sequence_list (mc_config_t *mc_config, const gchar *group, const gchar *param) +{ + const gchar *separator_str = " "; + gchar *str; + gchar **encoded_list; + GPtrArray *decoded_list; + + if (mc_config == NULL || group == NULL || param == NULL) + return NULL; + + str = g_key_file_get_value (mc_config->handle, group, param, NULL); + if (str == NULL) + return NULL; + + encoded_list = g_strsplit (str, separator_str, -1); + g_free (str); + + decoded_list = g_ptr_array_new (); + + // Decode each element. Skip empty strings or where decoding fails. + for (gchar **p = encoded_list; *p != NULL; p++) + { + GString *decoded; + + decoded = decode_controls (*p); + if (decoded != NULL) + g_ptr_array_add (decoded_list, decoded); + } + + g_strfreev (encoded_list); + + return decoded_list; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/lib/mcconfig/paths.c b/lib/mcconfig/paths.c index 5f130573b6..857a82e31e 100644 --- a/lib/mcconfig/paths.c +++ b/lib/mcconfig/paths.c @@ -59,6 +59,7 @@ static const struct { &mc_config_str, MC_CONFIG_FILE }, { &mc_config_str, MC_FHL_INI_FILE }, { &mc_config_str, MC_HOTLIST_FILE }, + { &mc_config_str, GLOBAL_KEYDEF_FILE }, { &mc_config_str, GLOBAL_KEYMAP_FILE }, { &mc_config_str, MC_USERMENU_FILE }, { &mc_config_str, EDIT_HOME_MENU }, @@ -308,3 +309,72 @@ mc_config_get_full_vpath (const char *config_name) } /* --------------------------------------------------------------------------------------------- */ + +/** + * Get name of config file. + * + * @param subdir If not NULL, config is searched in specified subdir. + * @param config_file_name If relative, file if searched in standard paths. + * @param suffix If file name does not end in this suffix then append it. + * + * @return newly allocated string with config name or NULL if file is not found. + */ + +char * +mc_config_get_full_config_name (const char *subdir, const char *config_file_name, + const char *suffix) +{ + char *lc_basename, *ret; + char *file_name; + + if (config_file_name == NULL) + return NULL; + + // check for suffix + if (suffix != NULL && g_str_has_suffix (config_file_name, suffix)) + file_name = g_strdup (config_file_name); + else + file_name = g_strconcat (config_file_name, suffix, (char *) NULL); + + canonicalize_pathname (file_name); + + if (g_path_is_absolute (file_name)) + return file_name; + + lc_basename = g_path_get_basename (file_name); + g_free (file_name); + + if (lc_basename == NULL) + return NULL; + + if (subdir != NULL) + ret = g_build_filename (mc_config_get_path (), subdir, lc_basename, (char *) NULL); + else + ret = g_build_filename (mc_config_get_path (), lc_basename, (char *) NULL); + + if (exist_file (ret)) + { + g_free (lc_basename); + canonicalize_pathname (ret); + return ret; + } + g_free (ret); + + if (subdir != NULL) + ret = g_build_filename (mc_global.share_data_dir, subdir, lc_basename, (char *) NULL); + else + ret = g_build_filename (mc_global.share_data_dir, lc_basename, (char *) NULL); + + g_free (lc_basename); + + if (exist_file (ret)) + { + canonicalize_pathname (ret); + return ret; + } + + g_free (ret); + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/lib/mcconfig/set.c b/lib/mcconfig/set.c index 332f76417f..29ad7e8f70 100644 --- a/lib/mcconfig/set.c +++ b/lib/mcconfig/set.c @@ -24,6 +24,7 @@ #include "lib/global.h" #include "lib/strutil.h" +#include "lib/terminal.h" // encode_controls(), decode_controls() #include "lib/mcconfig.h" @@ -156,3 +157,48 @@ mc_config_set_int_list (mc_config_t *mc_config, const gchar *group, const gchar } /* --------------------------------------------------------------------------------------------- */ + +/* + * An interface matching the other mc_config_set_*_list methods. + * + * Sets space-separated values, each stored according to encode_controls(). + * + * Primarily useful for having escape sequences in easy-to-read format, including \e for the escape + * character without having to double-escape it, and without having to escape semicolons. + */ +void +mc_config_set_escape_sequence_list (mc_config_t *mc_config, const gchar *group, const gchar *param, + const GPtrArray *value) +{ + const gchar separator = ' '; + GString *str; + gboolean add_separator = FALSE; + + if (mc_config == NULL || group == NULL || param == NULL || value == NULL || value->len == 0) + return; + + str = g_string_new (""); + + for (guint i = 0; i < value->len; i++) + { + GString *p = (GString *) g_ptr_array_index (value, i); + + if (p->len != 0) + { + GString *encoded; + + encoded = encode_controls (p->str, p->len); + if (add_separator) + g_string_append_c (str, separator); + mc_g_string_concat (str, encoded); + g_string_free (encoded, TRUE); + add_separator = TRUE; + } + } + + g_key_file_set_value (mc_config->handle, group, param, str->str); + + g_string_free (str, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/lib/terminal.c b/lib/terminal.c index 92b200e805..676af69420 100644 --- a/lib/terminal.c +++ b/lib/terminal.c @@ -247,61 +247,144 @@ strip_ctrl_codes (char *s) } /* --------------------------------------------------------------------------------------------- */ -/** - * Convert "\E" -> esc character and ^x to control-x key and ^^ to ^ key + +/* + * Escape the string according to these rules: + * + * byte 0x00 => ^@ + * byte 0x01 => ^A + * ... + * byte 0x1A => ^Z + * byte 0x1B => \e - the decoder also accepts \E or ^[ + * byte 0x1C => ^\ + * byte 0x1D => ^] + * byte 0x1E => ^^ + * byte 0x1F => ^_ + * byte 0x7F => ^? + * + * \ => \\ + * ^ => \^ + * space => \s * - * @param p pointer to string + * @param s raw input string, possibly containing an embedded NUL byte + * @param maybe_len length of s, or -1 if '\0'-terminated * * @return newly allocated string */ -char * -convert_controls (const char *p) +GString * +encode_controls (const char *s, const ssize_t maybe_len) +{ + GString *ret; + size_t len; + + if (s == NULL) + return NULL; + + if (maybe_len < 0) + len = strlen (s); + else + len = (size_t) maybe_len; + + const char *end = s + len; + + ret = g_string_sized_new (len + 1); // expect that 1 character will need to be escaped + + for (; s < end; s++) + if (*s == 0x1B) + g_string_append (ret, "\\e"); + else if (*s >= 0 && *s < ' ') + { + g_string_append_c (ret, '^'); + g_string_append_c (ret, *s + 0x40); + } + else if (*s == 0x7F) + g_string_append (ret, "^?"); + else if (*s == '\\') + g_string_append (ret, "\\\\"); + else if (*s == '^') + g_string_append (ret, "\\^"); + else if (*s == ' ') + g_string_append (ret, "\\s"); + else + g_string_append_c (ret, *s); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Unescape control characters, performing the opposite of encode_controls(). + * + * In addition to the notation of encode_controls(), the following are also accepted: + * - \e and \E are the same as ^[, the escape character + * - ^a .. ^z are the same as their ^A .. ^Z counterparts, the 0x01 .. 0x1A control characters + * - a trailing unescaped ^ stands for itself, a literal caret + * + * @param s the escaped string + * + * @return newly allocated string, possibly containing an embedded NUL byte + */ + +GString * +decode_controls (const char *s) { - char *valcopy; - char *q; + GString *ret; - valcopy = g_strdup (p); + if (s == NULL) + return NULL; + + ret = g_string_new (NULL); - // Parse the escape special character - for (q = valcopy; *p != '\0';) - switch (*p) + for (; *s != '\0'; s++) + switch (*s) { case '\\': - p++; + s++; + if (*s == '\0') + goto err; - if (*p == 'e' || *p == 'E') + switch (*s) { - p++; - *q++ = ESC_CHAR; + case 'e': + case 'E': + g_string_append_c (ret, ESC_CHAR); + break; + case 's': + g_string_append_c (ret, ' '); + break; + default: + g_string_append_c (ret, *s); } break; case '^': - p++; - if (*p == '^') - *q++ = *p++; - else + s++; + if (*s == '\0') { - char c; - - c = *p | 0x20; - if (c >= 'a' && c <= 'z') - { - *q++ = c - 'a' + 1; - p++; - } - else if (*p != '\0') - p++; + g_string_append_c (ret, '^'); + s--; // will reprocess that '\0' } + else if (*s >= 0x40 && *s < 0x60) + g_string_append_c (ret, *s - 0x40); + else if (*s >= 'a' && *s <= 'z') + g_string_append_c (ret, *s - 'a' + 1); + else if (*s == '?') + g_string_append_c (ret, 0x7F); + else + goto err; break; default: - *q++ = *p++; + g_string_append_c (ret, *s); } - *q = '\0'; - return valcopy; + return ret; + +err: + g_string_free (ret, TRUE); + return NULL; } /* --------------------------------------------------------------------------------------------- */ diff --git a/lib/terminal.h b/lib/terminal.h index a5e4536351..f2f708014f 100644 --- a/lib/terminal.h +++ b/lib/terminal.h @@ -31,10 +31,8 @@ gboolean parse_csi (csi_command_t *out, const char **sptr, const char *end); char *strip_ctrl_codes (char *s); -/* Replaces "\\E" and "\\e" with "\033". Replaces "^" + [a-z] with - * ((char) 1 + (c - 'a')). The same goes for "^" + [A-Z]. - * Returns a newly allocated string. */ -char *convert_controls (const char *s); +GString *encode_controls (const char *s, const ssize_t len); +GString *decode_controls (const char *s); /*** inline functions ****************************************************************************/ diff --git a/lib/tty/key.c b/lib/tty/key.c index 68cae9bd90..a59a9ca7de 100644 --- a/lib/tty/key.c +++ b/lib/tty/key.c @@ -659,12 +659,15 @@ try_channels (gboolean set_timeout) /* --------------------------------------------------------------------------------------------- */ +/* + * len must contain seq's actual length. seq might contain an embedded NUL. + */ static key_def * -create_sequence (const char *seq, int code, int action) +create_sequence (const char *seq, gsize len, int code, int action) { key_def *base, *attach; - for (base = attach = NULL; *seq != '\0'; seq++) + for (base = attach = NULL; len != 0; seq++, len--) { key_def *p; @@ -678,7 +681,7 @@ create_sequence (const char *seq, int code, int action) p->code = code; p->child = NULL; p->next = NULL; - p->action = seq[1] == '\0' ? action : MCKEY_NOACTION; + p->action = len > 1 ? action : MCKEY_NOACTION; attach = p; } return base; @@ -692,7 +695,7 @@ define_sequences (const key_define_t *kd) int i; for (i = 0; kd[i].code != 0; i++) - define_sequence (kd[i].code, kd[i].seq, kd[i].action); + define_sequence (kd[i].code, kd[i].seq, -1, kd[i].action); } /* --------------------------------------------------------------------------------------------- */ @@ -1169,24 +1172,6 @@ getch_with_timeout (unsigned int delay_us) /* --------------------------------------------------------------------------------------------- */ -static void -learn_store_key (GString *buffer, int c) -{ - if (c == ESC_CHAR) - g_string_append (buffer, "\\e"); - else if (c < ' ') - { - g_string_append_c (buffer, '^'); - g_string_append_c (buffer, c + 'a' - 1); - } - else if (c == '^') - g_string_append (buffer, "^^"); - else - g_string_append_c (buffer, (char) c); -} - -/* --------------------------------------------------------------------------------------------- */ - static void k_dispose (key_def *k) { @@ -1602,23 +1587,32 @@ tty_keycode_to_keyname (const int keycode) /** * Return TRUE on success, FALSE on error. * An error happens if SEQ is a beginning of an existing longer sequence. + * + * maybe_len may contain seq's actual length, in which case seq might contain an embedded NUL. + * Otherwise, if len is negative then it's a standard NUL-terminated C string. */ gboolean -define_sequence (int code, const char *seq, int action) +define_sequence (int code, const char *seq, gssize maybe_len, int action) { + gsize len; key_def *base; - if (strlen (seq) > SEQ_BUFFER_LEN - 1) + if (maybe_len < 0) + len = strlen (seq); + else + len = (gsize) maybe_len; + + if (len > SEQ_BUFFER_LEN - 1) return FALSE; - for (base = keys; (base != NULL) && (*seq != '\0');) + for (base = keys; base != NULL && len != 0;) if (*seq == base->ch) { if (base->child == NULL) { - if (*(seq + 1) != '\0') - base->child = create_sequence (seq + 1, code, action); + if (len > 1) + base->child = create_sequence (seq + 1, len - 1, code, action); else { // The sequence matches an existing one. @@ -1630,6 +1624,7 @@ define_sequence (int code, const char *seq, int action) base = base->child; seq++; + len--; } else { @@ -1637,18 +1632,18 @@ define_sequence (int code, const char *seq, int action) base = base->next; else { - base->next = create_sequence (seq, code, action); + base->next = create_sequence (seq, len, code, action); return TRUE; } } - if (*seq == '\0') + if (len == 0) { // Attempt to redefine a sequence with a shorter sequence. return FALSE; } - keys = create_sequence (seq, code, action); + keys = create_sequence (seq, len, code, action); return TRUE; } @@ -2107,7 +2102,8 @@ tty_getch (void) /* --------------------------------------------------------------------------------------------- */ -char * +/* Returned string might contain embedded NUL characters. */ +GString * learn_key (void) { // LEARN_TIMEOUT in ms @@ -2124,7 +2120,7 @@ learn_key (void) c = tty_lowlevel_getch (); while (c == -1) c = tty_lowlevel_getch (); // Sanity check, should be unnecessary - learn_store_key (buffer, c); + g_string_append_c (buffer, c); end_time = g_get_monotonic_time () + LEARN_TIMEOUT * MC_USEC_PER_MSEC; @@ -2148,12 +2144,16 @@ learn_key (void) } if (c == -1) break; - learn_store_key (buffer, c); + g_string_append_c (buffer, c); } tty_keypad (TRUE); tty_nodelay (FALSE); - return g_string_free (buffer, buffer->len == 0); + if (buffer->len != 0) + return buffer; + + g_string_free (buffer, TRUE); + return NULL; #undef LEARN_TIMEOUT } diff --git a/lib/tty/key.h b/lib/tty/key.h index 3d902678ef..3ebfc92bf8 100644 --- a/lib/tty/key.h +++ b/lib/tty/key.h @@ -68,7 +68,7 @@ extern gboolean bracketed_pasting_in_progress; /*** declarations of public functions ************************************************************/ -gboolean define_sequence (int code, const char *seq, int action); +gboolean define_sequence (int code, const char *seq, gssize len, int action); void init_key (void); void init_key_input_fd (void); @@ -96,7 +96,7 @@ void channels_down (void); void load_xtra_key_defines (void); /* Learn a single key */ -char *learn_key (void); +GString *learn_key (void); /* Returns a key code (interpreted) */ int get_key_code (int nodelay); diff --git a/lib/tty/keyxdef.c b/lib/tty/keyxdef.c index 33369fbef1..fe792b6fbd 100644 --- a/lib/tty/keyxdef.c +++ b/lib/tty/keyxdef.c @@ -425,7 +425,7 @@ load_qnx_key_defines (void) { if (*__QTISSTR (str_idx)) { - define_sequence (xtra_key_defines[idx].mc_code, __QTISSTR (str_idx), + define_sequence (xtra_key_defines[idx].mc_code, __QTISSTR (str_idx), -1, MCKEY_NOACTION); } } diff --git a/lib/tty/mouse.c b/lib/tty/mouse.c index 0ab47639ee..b6bd7831e9 100644 --- a/lib/tty/mouse.c +++ b/lib/tty/mouse.c @@ -93,9 +93,9 @@ init_mouse (void) case MOUSE_XTERM_NORMAL_TRACKING: case MOUSE_XTERM_BUTTON_EVENT_TRACKING: if (xmouse_seq != NULL) - define_sequence (MCKEY_MOUSE, xmouse_seq, MCKEY_NOACTION); + define_sequence (MCKEY_MOUSE, xmouse_seq, -1, MCKEY_NOACTION); if (xmouse_extended_seq != NULL) - define_sequence (MCKEY_EXTENDED_MOUSE, xmouse_extended_seq, MCKEY_NOACTION); + define_sequence (MCKEY_EXTENDED_MOUSE, xmouse_extended_seq, -1, MCKEY_NOACTION); break; default: diff --git a/lib/tty/tty-slang.c b/lib/tty/tty-slang.c index 0b8e61e2f3..8aaa0b3102 100644 --- a/lib/tty/tty-slang.c +++ b/lib/tty/tty-slang.c @@ -163,7 +163,7 @@ do_define_key (int code, const char *strcap) seq = SLtt_tgetstr ((SLFUTURE_CONST char *) strcap); if (seq != NULL) - define_sequence (code, seq, MCKEY_NOACTION); + define_sequence (code, seq, -1, MCKEY_NOACTION); } /* --------------------------------------------------------------------------------------------- */ diff --git a/misc/Makefile.am b/misc/Makefile.am index b02fcc5b35..df0d737faf 100644 --- a/misc/Makefile.am +++ b/misc/Makefile.am @@ -26,6 +26,7 @@ SCRIPTS_OUT = \ LIBFILES_CONST = \ filehighlight.ini \ + mc.keydef \ mc.default.keymap \ mc.emacs.keymap \ mc.vim.keymap diff --git a/misc/mc.keydef b/misc/mc.keydef new file mode 100644 index 0000000000..5e045bb01b --- /dev/null +++ b/misc/mc.keydef @@ -0,0 +1,205 @@ +# This file lets mc learn the meaning of the incoming escape sequences. +# +# Keys must not repeat within a section. You can define multiple escape +# sequences for a key, separated by spaces. +# +# The following notation is available: +# +# char (byte) ~ notation in this file +# +# 0x00 ~ ^@ +# 0x01 ~ ^A +# .... .. +# 0x1A ~ ^Z +# 0x1B ~ ^[ or \e or \E +# 0x1C ~ ^\ +# 0x1D ~ ^] +# 0x1E ~ ^^ +# 0x1F ~ ^_ +# 0x7F ~ ^? +# +# \ ~ \\ +# ^ ~ \^ +# space ~ \s +# +# A trailing ^ character of an escape sequence can be left unbackslashed, +# useful for denoting sequences like urxvt's ctrl-f1: \e[11^ +# +# To find out the sequence generated by your terminal, launch the command +# +# tput smkx; cat +# +# and press a special key. You should see a sequence beginning with ^[ which +# represents the escape character. Copy-paste the result to this file. +# +# You may replace the leading ^[ with \e or \E but you don't have to. +# +# If you don't get such escape sequences, this command could help: +# +# stty echoctl +# +# In order to catch what backspace or ctrl-backspace sends, try this: +# +# tput smkx; showkey -a +# +# Once done with either "cat" or "showkey -a", reset the terminal's defaults: +# +# tput rmkx + +[console] +insert = \e[2~ +f11 = \e[23~ +f12 = \e[24~ +f13 = \e[25~ +f14 = \e[26~ +f15 = \e[28~ +f16 = \e[29~ +f17 = \e[31~ +f18 = \e[32~ +f19 = \e[33~ +f20 = \e[34~ +kpleft = \eOt +kpright = \eOv +kpup = \eOx +kpdown = \eOr +kphome = \eOw +kpend = \eOq +kpnpage = \eOs +kpppage = \eOy +kpplus = \eOl +kpminus = \eOS +kpasterisk = \eOR +kpinsert = \eOp +kpdelete = \eOn +kpenter = \eOM +kpslash = \eOQ +kpnumlock = \eOP + +[linux] +copy = console + +[xterm] +insert = \e[2~ +f11 = \e[23~ \eO2P \eO1;2P \e[1;2P +f12 = \e[24~ \eO2Q \eO1;2Q \e[1;2Q +f13 = \e[25~ \eO2R \eO1;2R \e[1;2R +f14 = \e[26~ \eO2S \eO1;2S \e[1;2S +f15 = \e[28~ \e[15;2~ +f16 = \e[29~ \e[17;2~ +f17 = \e[31~ \e[18;2~ +f18 = \e[32~ \e[19;2~ +f19 = \e[33~ \e[20;2~ +f20 = \e[34~ \e[21;2~ +kpleft = \eOt +kpright = \eOv +kpup = \eOx +kpdown = \eOr +kphome = \eOw +kpend = \eOq +kpnpage = \eOs +kpppage = \eOy +kpplus = \eOk +kpminus = \eOm +kpasterisk = \eOj +delete = \e[3~ +bs = ^? +home = \e[1~ \e[7~ \eOH \e[H +end = \e[4~ \e[8~ \eOF \e[F +pgdn = \e[6~ +pgup = \e[5~ + +# Arrows for both keypad modes (application and normal). +up = \e[A \eOA +down = \e[B \eOB +right = \e[C \eOC +left = \e[D \eOD + +[alacritty] +copy = xterm + +[gnome] +copy = xterm + +[rxvt] +copy = xterm + +[xterm-new] +copy = xterm + +[xterm-clear] +copy = xterm + +[xterm-color] +copy = xterm + +[xterm-256color] +copy = xterm + +[xterm-direct] +copy = xterm + +[xterm-direct16] +copy = xterm + +[xterm-direct256] +copy = xterm + +[screen] +copy = xterm + +[screen-256color] +copy = xterm + +[tmux] +copy = xterm + +[tmux-256color] +copy = xterm + +[ibmpc3] +f11 = \e[Y +f12 = \e[Z +f13 = \e[a +f14 = \e[b +f15 = \e[c +f16 = \e[d +f17 = \e[e +f18 = \e[f +f19 = \e[g +f20 = \e[h +bs = ^H +end = \e[F +kpplus = \e[+ +kpminus = \e[- +kpasterisk = \e[* + +[cons25] +f11 = \e[Y +f12 = \e[Z +f13 = \e[a +f14 = \e[b +f15 = \e[c +f16 = \e[d +f17 = \e[e +f18 = \e[f +f19 = \e[g +f20 = \e[h + +[st] +shift-right = \e[1;2C +shift-left = \e[1;2D +shift-up = \e[1;2A +shift-down = \e[1;2B +alt-shift-right = \e[1;4C +alt-shift-left = \e[1;4D +alt-shift-up = \e[1;4A +alt-shift-down = \e[1;4B + +[st-256color] +copy = st + +[st-git-256color] +copy = st + +[xterm-kitty] +copy = xterm diff --git a/misc/mc.lib b/misc/mc.lib index a7eb67e1de..7a9f6e1334 100644 --- a/misc/mc.lib +++ b/misc/mc.lib @@ -11,165 +11,3 @@ mcedit=%filename:%lineno [Special dirs] list=/afs;/coda;/:;/...;/net - -[terminal:console] -insert=\\e[2~ -f11=\\e[23~ -f12=\\e[24~ -f13=\\e[25~ -f14=\\e[26~ -f15=\\e[28~ -f16=\\e[29~ -f17=\\e[31~ -f18=\\e[32~ -f19=\\e[33~ -f20=\\e[34~ -kpleft=\\eOt -kpright=\\eOv -kpup=\\eOx -kpdown=\\eOr -kphome=\\eOw -kpend=\\eOq -kpnpage=\\eOs -kpppage=\\eOy -kpplus=\\eOl -kpminus=\\eOS -kpasterisk=\\eOR -kpinsert=\\eOp -kpdelete=\\eOn -kpenter=\\eOM -kpslash=\\eOQ -kpnumlock=\\eOP - -[terminal:linux] -copy=console - -[terminal:xterm] -insert=\\e[2~ -f11=\\e[23~;\\eO2P;\\eO1\;2P;\\e[1\;2P -f12=\\e[24~;\\eO2Q;\\eO1\;2Q;\\e[1\;2Q -f13=\\e[25~;\\eO2R;\\eO1\;2R;\\e[1\;2R -f14=\\e[26~;\\eO2S;\\eO1\;2S;\\e[1\;2S -f15=\\e[28~;\\e[15\;2~ -f16=\\e[29~;\\e[17\;2~ -f17=\\e[31~;\\e[18\;2~ -f18=\\e[32~;\\e[19\;2~ -f19=\\e[33~;\\e[20\;2~ -f20=\\e[34~;\\e[21\;2~ -kpleft=\\eOt -kpright=\\eOv -kpup=\\eOx -kpdown=\\eOr -kphome=\\eOw -kpend=\\eOq -kpnpage=\\eOs -kpppage=\\eOy -kpplus=\\eOk -kpminus=\\eOm -kpasterisk=\\eOj -delete=\\e[3~ -bs= -home=\\e[1~;\\e[7~;\\eOH;\\e[H -end=\\eOF;\\e[F;\\e[4~;\\e[8~ -pgdn=\\e[6~ -pgup=\\e[5~ - -# Arrows for both keypad modes (application and normal). -up=\\e[A -up=\\eOA -down=\\e[B -down=\\eOB -right=\\e[C -right=\\eOC -left=\\e[D -left=\\eOD - -[terminal:alacritty] -copy=xterm - -[terminal:gnome] -copy=xterm - -[terminal:rxvt] -copy=xterm - -[terminal:xterm-new] -copy=xterm - -[terminal:xterm-clear] -copy=xterm - -[terminal:xterm-color] -copy=xterm - -[terminal:xterm-256color] -copy=xterm - -[terminal:xterm-direct] -copy=xterm - -[terminal:xterm-direct16] -copy=xterm - -[terminal:xterm-direct256] -copy=xterm - -[terminal:screen] -copy=xterm - -[terminal:screen-256color] -copy=xterm - -[terminal:tmux] -copy=xterm - -[terminal:tmux-256color] -copy=xterm - -[terminal:ibmpc3] -f11=\\e[Y -f12=\\e[Z -f13=\\e[a -f14=\\e[b -f15=\\e[c -f16=\\e[d -f17=\\e[e -f18=\\e[f -f19=\\e[g -f20=\\e[h -bs=^h -end=\\e[F -kpplus=\\e[+ -kpminus=\\e[- -kpasterisk=\\e[* - -[terminal:cons25] -f11=\\e[Y -f12=\\e[Z -f13=\\e[a -f14=\\e[b -f15=\\e[c -f16=\\e[d -f17=\\e[e -f18=\\e[f -f19=\\e[g -f20=\\e[h - -[terminal:st] -shift-right=\\e[1\;2C -shift-left=\\e[1\;2D -shift-up=\\e[1\;2A -shift-down=\\e[1\;2B -alt-shift-right=\\e[1\;4C -alt-shift-left=\\e[1\;4D -alt-shift-up=\\e[1\;4A -alt-shift-down=\\e[1\;4B - -[terminal:st-256color] -copy=st - -[terminal:st-git-256color] -copy=st - -[terminal:xterm-kitty] -copy=xterm diff --git a/src/args.c b/src/args.c index a72fab5de6..5e66a0d6d5 100644 --- a/src/args.c +++ b/src/args.c @@ -54,6 +54,9 @@ gboolean mc_args__nomouse = FALSE; /* Force colors, only used by Slang */ gboolean mc_args__force_colors = FALSE; +/* Don't load keydef from file and use default one */ +gboolean mc_args__nokeydef = FALSE; + /* Don't load keymap from file and use default one */ gboolean mc_args__nokeymap = FALSE; @@ -62,6 +65,9 @@ char *mc_args__last_wd_file = NULL; /* when enabled NETCODE, use following file as logfile */ char *mc_args__netfs_logfile = NULL; +/* keydef file */ +char *mc_args__keydef_file = NULL; + /* keymap file */ char *mc_args__keymap_file = NULL; @@ -283,6 +289,26 @@ static const GOptionEntry argument_terminal_table[] = { NULL, }, + { + "keydef", + 'k', + ARGS_TERM_OPTIONS, + G_OPTION_ARG_STRING, + &mc_args__keydef_file, + N_ ("Load definitions of key escape sequences from specified file"), + N_ (""), + }, + + { + "nokeydef", + '\0', + ARGS_TERM_OPTIONS, + G_OPTION_ARG_NONE, + &mc_args__nokeydef, + N_ ("Don't load definitions of key escape sequences from file, use defaults"), + NULL, + }, + { "keymap", 'K', diff --git a/src/args.h b/src/args.h index db7c2c018e..58667a6f77 100644 --- a/src/args.h +++ b/src/args.h @@ -15,9 +15,11 @@ extern gboolean mc_args__force_xterm; extern gboolean mc_args__nomouse; extern gboolean mc_args__force_colors; +extern gboolean mc_args__nokeydef; extern gboolean mc_args__nokeymap; extern char *mc_args__last_wd_file; extern char *mc_args__netfs_logfile; +extern char *mc_args__keydef_file; extern char *mc_args__keymap_file; /* diff --git a/src/keymap.c b/src/keymap.c index 35ef2759eb..600b478851 100644 --- a/src/keymap.c +++ b/src/keymap.c @@ -742,75 +742,6 @@ load_keymap_from_section (const char *section_name, GArray *keymap, mc_config_t g_strfreev (keys); } -/* --------------------------------------------------------------------------------------------- */ -/** - * Get name of config file. - * - * @param subdir If not NULL, config is also searched in specified subdir. - * @param config_file_name If relative, file if searched in standard paths. - * - * @return newly allocated string with config name or NULL if file is not found. - */ - -static char * -load_setup_get_full_config_name (const char *subdir, const char *config_file_name) -{ - /* - TODO: IMHO, in future, this function shall be placed in mcconfig module. - */ - char *lc_basename, *ret; - char *file_name; - - if (config_file_name == NULL) - return NULL; - - // check for .keymap suffix - if (g_str_has_suffix (config_file_name, ".keymap")) - file_name = g_strdup (config_file_name); - else - file_name = g_strconcat (config_file_name, ".keymap", (char *) NULL); - - canonicalize_pathname (file_name); - - if (g_path_is_absolute (file_name)) - return file_name; - - lc_basename = g_path_get_basename (file_name); - g_free (file_name); - - if (lc_basename == NULL) - return NULL; - - if (subdir != NULL) - ret = g_build_filename (mc_config_get_path (), subdir, lc_basename, (char *) NULL); - else - ret = g_build_filename (mc_config_get_path (), lc_basename, (char *) NULL); - - if (exist_file (ret)) - { - g_free (lc_basename); - canonicalize_pathname (ret); - return ret; - } - g_free (ret); - - if (subdir != NULL) - ret = g_build_filename (mc_global.share_data_dir, subdir, lc_basename, (char *) NULL); - else - ret = g_build_filename (mc_global.share_data_dir, lc_basename, (char *) NULL); - - g_free (lc_basename); - - if (exist_file (ret)) - { - canonicalize_pathname (ret); - return ret; - } - - g_free (ret); - return NULL; -} - /* --------------------------------------------------------------------------------------------- */ /** Create new mc_config object from specified ini-file or @@ -840,6 +771,7 @@ load_setup_get_keymap_profile_config (gboolean load_from_file) /* TODO: IMHO, in future, this function shall be placed in mcconfig module. */ + const char *suffix = ".keymap"; mc_config_t *keymap_config; char *share_keymap, *sysconfig_keymap; char *fname, *fname2; @@ -863,7 +795,7 @@ load_setup_get_keymap_profile_config (gboolean load_from_file) // then load and merge one of user-defined keymap // 3) --keymap= - fname = load_setup_get_full_config_name (NULL, mc_args__keymap_file); + fname = mc_config_get_full_config_name (NULL, mc_args__keymap_file, suffix); if (fname != NULL && strcmp (fname, sysconfig_keymap) != 0 && strcmp (fname, share_keymap) != 0) { load_setup_init_config_from_file (&keymap_config, fname, TRUE); @@ -872,7 +804,7 @@ load_setup_get_keymap_profile_config (gboolean load_from_file) g_free (fname); // 4) getenv("MC_KEYMAP") - fname = load_setup_get_full_config_name (NULL, g_getenv ("MC_KEYMAP")); + fname = mc_config_get_full_config_name (NULL, g_getenv ("MC_KEYMAP"), suffix); if (fname != NULL && strcmp (fname, sysconfig_keymap) != 0 && strcmp (fname, share_keymap) != 0) { load_setup_init_config_from_file (&keymap_config, fname, TRUE); @@ -884,7 +816,7 @@ load_setup_get_keymap_profile_config (gboolean load_from_file) // 5) main config; [Midnight Commander] -> keymap fname2 = mc_config_get_string (mc_global.main_config, CONFIG_APP_SECTION, "keymap", NULL); if (fname2 != NULL && *fname2 != '\0') - fname = load_setup_get_full_config_name (NULL, fname2); + fname = mc_config_get_full_config_name (NULL, fname2, suffix); g_free (fname2); if (fname != NULL && strcmp (fname, sysconfig_keymap) != 0 && strcmp (fname, share_keymap) != 0) { diff --git a/src/learn.c b/src/learn.c index 86b93f849a..f544b4c6fc 100644 --- a/src/learn.c +++ b/src/learn.c @@ -33,12 +33,13 @@ #include #include "lib/global.h" +#include "lib/fileloc.h" #include "lib/tty/tty.h" #include "lib/tty/key.h" #include "lib/mcconfig.h" #include "lib/strutil.h" -#include "lib/terminal.h" // convert_controls() +#include "lib/terminal.h" // escape_controls() #include "lib/util.h" // MC_PTR_FREE #include "lib/widget.h" @@ -62,7 +63,7 @@ typedef struct Widget *button; Widget *label; gboolean ok; - char *sequence; + GString *sequence; // might contain embedded NUL } learnkey_t; /*** forward declarations (file scope functions) *************************************************/ @@ -85,7 +86,7 @@ static int learn_button (WButton *button, int action) { WDialog *d; - char *seq; + GString *seq; (void) button; @@ -109,20 +110,25 @@ learn_button (WButton *button, int action) */ gboolean seq_ok = FALSE; - if (strcmp (seq, "\\e") != 0 && strcmp (seq, "\\e\\e") != 0 && strcmp (seq, "^m") != 0 - && strcmp (seq, "^i") != 0 && (seq[1] != '\0' || *seq < ' ' || *seq > '~')) + if (strcmp (seq->str, "\\e") != 0 && strcmp (seq->str, "\\e\\e") != 0 + && strcmp (seq->str, "^m") != 0 && strcmp (seq->str, "^i") != 0 + && (seq->str[1] != '\0' || *seq->str < ' ' || *seq->str > '~')) + { + seq_ok = define_sequence (key_name_conv_tab[action - B_USER].code, seq->str, seq->len, + MCKEY_NOACTION); + } + + if (seq_ok) { learnchanged = TRUE; learnkeys[action - B_USER].sequence = seq; - seq = convert_controls (seq); - seq_ok = define_sequence (key_name_conv_tab[action - B_USER].code, seq, MCKEY_NOACTION); } - - if (!seq_ok) + else + { message (D_NORMAL, _ ("Warning"), - _ ("Cannot accept this key.\nYou have entered \"%s\""), seq); - - g_free (seq); + _ ("Cannot accept this key.\nYou have entered \"%s\""), seq->str); + g_string_free (seq, TRUE); + } } dlg_run_done (d); @@ -360,34 +366,34 @@ static void learn_save (void) { int i; - char *section; + mc_config_t *keydef_config; + char *fname; + GPtrArray *list; gboolean profile_changed = FALSE; - section = g_strconcat ("terminal:", getenv ("TERM"), (char *) NULL); + fname = mc_config_get_full_path (GLOBAL_KEYDEF_FILE); + keydef_config = mc_config_init (fname, FALSE); + if (exist_file (fname)) + mc_config_read_file (keydef_config, fname, FALSE, TRUE); + + list = g_ptr_array_new (); for (i = 0; i < learn_total; i++) if (learnkeys[i].sequence != NULL) { - char *esc_str; - - esc_str = str_escape (learnkeys[i].sequence, -1, ";\\", TRUE); - mc_config_set_string_raw_value (mc_global.main_config, section, - key_name_conv_tab[i].name, esc_str); - g_free (esc_str); - + g_ptr_array_add (list, learnkeys[i].sequence); + mc_config_set_escape_sequence_list (keydef_config, getenv ("TERM"), + key_name_conv_tab[i].name, list); + g_ptr_array_remove_index_fast (list, 0); profile_changed = TRUE; } - /* On the one hand no good idea to save the complete setup but - * without 'Auto save setup' the new key-definitions will not be - * saved unless the user does an 'Options/Save Setup'. - * On the other hand a save-button that does not save anything to - * disk is much worse. - */ if (profile_changed) - mc_config_save_file (mc_global.main_config, NULL); + mc_config_save_file (keydef_config, NULL); - g_free (section); + g_ptr_array_free (list, TRUE); + g_free (fname); + mc_config_deinit (keydef_config); } /* --------------------------------------------------------------------------------------------- */ diff --git a/src/main.c b/src/main.c index a67270150e..d1689bb5c9 100644 --- a/src/main.c +++ b/src/main.c @@ -358,7 +358,7 @@ main (int argc, char *argv[]) tty_init (!mc_args__nomouse, mc_global.tty.xterm_flag); // Removing this from the X code let's us type C-c - load_key_defs (); + load_keydefs (); keymap_load (!mc_args__nokeymap); diff --git a/src/setup.c b/src/setup.c index aaa32bd6ad..56167036e2 100644 --- a/src/setup.c +++ b/src/setup.c @@ -38,7 +38,7 @@ #include "lib/tty/key.h" #include "lib/mcconfig.h" // num_history_items_recorded #include "lib/fileloc.h" -#include "lib/terminal.h" // convert_controls() +#include "lib/terminal.h" // unescape_controls() #include "lib/timefmt.h" #include "lib/util.h" #include "lib/charsets.h" @@ -452,6 +452,14 @@ static const struct /*** file scope functions ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ +static void +setup__gstring_free (gpointer data) +{ + g_string_free ((GString *) data, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + static const char * setup__is_cfg_group_must_panel_config (const char *grp) { @@ -612,25 +620,23 @@ load_layout (void) /* --------------------------------------------------------------------------------------------- */ static void -load_keys_from_section (const char *terminal, mc_config_t *cfg) +load_keydefs_from_section (const char *terminal, mc_config_t *cfg) { - char *section_name; gchar **profile_keys, **keys; - char *valcopy, *value; + char *valcopy; if (terminal == NULL) return; - section_name = g_strconcat ("terminal:", terminal, (char *) NULL); - keys = mc_config_get_keys (cfg, section_name, NULL); + keys = mc_config_get_keys (cfg, terminal, NULL); for (profile_keys = keys; *profile_keys != NULL; profile_keys++) { - // copy=other causes all keys from [terminal:other] to be loaded. + // copy=other causes all keys from [other] to be loaded. if (g_ascii_strcasecmp (*profile_keys, "copy") == 0) { - valcopy = mc_config_get_string (cfg, section_name, *profile_keys, ""); - load_keys_from_section (valcopy, cfg); + valcopy = mc_config_get_string (cfg, terminal, *profile_keys, ""); + load_keydefs_from_section (valcopy, cfg); g_free (valcopy); continue; } @@ -639,34 +645,46 @@ load_keys_from_section (const char *terminal, mc_config_t *cfg) if (key_code != 0) { - gchar **values; + GPtrArray *values; - values = mc_config_get_string_list (cfg, section_name, *profile_keys, NULL); + values = mc_config_get_escape_sequence_list (cfg, terminal, *profile_keys); if (values != NULL) { - gchar **curr_values; + g_ptr_array_set_free_func (values, setup__gstring_free); - for (curr_values = values; *curr_values != NULL; curr_values++) + for (guint i = 0; i < values->len; i++) { - valcopy = convert_controls (*curr_values); - define_sequence (key_code, valcopy, MCKEY_NOACTION); - g_free (valcopy); + const GString *curr_value = (GString *) g_ptr_array_index (values, i); + + if (curr_value->len != 0) + define_sequence (key_code, curr_value->str, curr_value->len, + MCKEY_NOACTION); } - g_strfreev (values); - } - else - { - value = mc_config_get_string (cfg, section_name, *profile_keys, ""); - valcopy = convert_controls (value); - define_sequence (key_code, valcopy, MCKEY_NOACTION); - g_free (valcopy); - g_free (value); + g_ptr_array_free (values, TRUE); } } } g_strfreev (keys); - g_free (section_name); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +load_keydefs_from_file (const char *fname) +{ + mc_config_t *keydef_config; + + if (!exist_file (fname)) + return; + + keydef_config = mc_config_init (NULL, TRUE); + mc_config_read_file (keydef_config, fname, TRUE, TRUE); + + load_keydefs_from_section ("_common_", keydef_config); + load_keydefs_from_section (getenv ("TERM"), keydef_config); + + mc_config_deinit (keydef_config); } /* --------------------------------------------------------------------------------------------- */ @@ -1086,24 +1104,69 @@ setup_save_config_show_error (const char *filename, GError **mcerror) /* --------------------------------------------------------------------------------------------- */ void -load_key_defs (void) +load_keydefs (void) { - /* - * Load keys from mc.lib before ${XDG_CONFIG_HOME}/mc/ini, so that the user - * definitions override global settings. - */ - mc_config_t *mc_global_config; - - mc_global_config = mc_config_init (mc_global.profile_name, FALSE); - if (mc_global_config != NULL) + // This method is closely based on keymap.c's load_setup_get_keymap_profile_config(). + // A main difference is that we don't collect the contents of all the files into a common + // mc_config_t object, but rather we process the key definitions from each file on the spot. + // This is in order to make the "copy" rule work on a given file only. + + const char *suffix = ".keydef"; + char *share_keydef, *sysconfig_keydef; + char *fname, *fname2; + + // load and merge global keydefs + + // 1) /usr/share/mc (mc_global.share_data_dir) + share_keydef = g_build_filename (mc_global.share_data_dir, GLOBAL_KEYDEF_FILE, (char *) NULL); + load_keydefs_from_file (share_keydef); + + // 2) /etc/mc (mc_global.sysconfig_dir) + sysconfig_keydef = + g_build_filename (mc_global.sysconfig_dir, GLOBAL_KEYDEF_FILE, (char *) NULL); + load_keydefs_from_file (sysconfig_keydef); + + // then load and merge one of user-defined keydef + + // 3) --keydef= + fname = mc_config_get_full_config_name (NULL, mc_args__keydef_file, suffix); + if (fname != NULL && strcmp (fname, sysconfig_keydef) != 0 && strcmp (fname, share_keydef) != 0) { - load_keys_from_section ("general", mc_global_config); - load_keys_from_section (getenv ("TERM"), mc_global_config); - mc_config_deinit (mc_global_config); + load_keydefs_from_file (fname); + goto done; } + g_free (fname); + + // 4) getenv("MC_KEYDEF") + fname = mc_config_get_full_config_name (NULL, g_getenv ("MC_KEYDEF"), suffix); + if (fname != NULL && strcmp (fname, sysconfig_keydef) != 0 && strcmp (fname, share_keydef) != 0) + { + load_keydefs_from_file (fname); + goto done; + } + + MC_PTR_FREE (fname); + + // 5) main config; [Midnight Commander] -> keydef + fname2 = mc_config_get_string (mc_global.main_config, CONFIG_APP_SECTION, "keydef", NULL); + if (fname2 != NULL && *fname2 != '\0') + fname = mc_config_get_full_config_name (NULL, fname2, suffix); + g_free (fname2); + if (fname != NULL && strcmp (fname, sysconfig_keydef) != 0 && strcmp (fname, share_keydef) != 0) + { + load_keydefs_from_file (fname); + goto done; + } + g_free (fname); + + // 6) ${XDG_CONFIG_HOME}/mc/mc.keydef + fname = mc_config_get_full_path (GLOBAL_KEYDEF_FILE); + load_keydefs_from_file (fname); - load_keys_from_section ("general", mc_global.main_config); - load_keys_from_section (getenv ("TERM"), mc_global.main_config); +done: + g_free (fname); + g_free (sysconfig_keydef); + g_free (share_keydef); } /* --------------------------------------------------------------------------------------------- */ diff --git a/src/setup.h b/src/setup.h index 11e02610e1..0a780767b1 100644 --- a/src/setup.h +++ b/src/setup.h @@ -149,7 +149,7 @@ gboolean save_setup (gboolean save_options, gboolean save_panel_options); void done_setup (void); void setup_save_config_show_error (const char *filename, GError **mcerror); -void load_key_defs (void); +void load_keydefs (void); #ifdef ENABLE_VFS_FTP char *load_anon_passwd (void); #endif diff --git a/tests/lib/terminal.c b/tests/lib/terminal.c index a0f0be3dd5..1536bd06ad 100644 --- a/tests/lib/terminal.c +++ b/tests/lib/terminal.c @@ -110,6 +110,102 @@ END_TEST /* --------------------------------------------------------------------------------------------- */ +static const struct encode_source +{ + const gchar *input_string; + ssize_t input_string_len; + const gchar *expected_string; +} encode_source[] = { + { "\001", -1, "^A" }, + { "\032", -1, "^Z" }, + { "\033", -1, "\\e" }, + { "\034", -1, "^\\" }, + { "\035", -1, "^]" }, + { "\036", -1, "^^" }, + { "\037", -1, "^_" }, + { "\177", -1, "^?" }, + { " ", -1, "\\s" }, + { "\\", -1, "\\\\" }, + { "^", -1, "\\^" }, + { "\000", 1, "^@" }, + { "ab\000\000cd\000\000ef", 7, "ab^@^@cd^@" }, +}; + +/* --------------------------------------------------------------------------------------------- */ + +START_PARAMETRIZED_TEST (test_encode_controls, encode_source) +{ + GString *actual_string; + + actual_string = encode_controls (data->input_string, data->input_string_len); + mctest_assert_str_eq (actual_string->str, data->expected_string); + + g_string_free (actual_string, TRUE); +} +END_PARAMETRIZED_TEST + +/* --------------------------------------------------------------------------------------------- */ + +static const struct decode_source +{ + const gchar *input_string; + const gchar *expected_string; +} decode_source[] = { + { "^A", "\001" }, + { "^Z", "\032" }, + { "^[", "\033" }, + { "\\e", "\033" }, + { "\\E", "\033" }, + { "^\\", "\034" }, + { "^]", "\035" }, + { "^^", "\036" }, + { "^_", "\037" }, + { "^?", "\177" }, + { "\\s", " " }, + { "\\\\", "\\" }, + { "\\^", "^" }, + { "^A^[\\e\\E^\\^^^?", "\001\033\033\033\034\036\177" }, + { "\\\\\\\\\\\\", "\\\\\\" }, + { "^^^^^^", "\036\036\036" }, + { "^^^^^\\^\\\\\\\\\\\\^\\^^^^^", "\036\036\034\034\\\\^^\036\036" }, + { "trailing^", "trailing^" }, +}; + +START_PARAMETRIZED_TEST (test_decode_controls, decode_source) +{ + GString *actual_string; + + actual_string = decode_controls (data->input_string); + mctest_assert_str_eq (actual_string->str, data->expected_string); + g_string_free (actual_string, TRUE); +} +END_PARAMETRIZED_TEST + +/* --------------------------------------------------------------------------------------------- */ + +static const struct decode_source_len +{ + const gchar *input_string; + const gchar *expected_string; + ssize_t expected_string_len; +} decode_source_len[] = { + { "^@", "\000", 1 }, + { "ab^@^@cd^@", "ab\000\000cd\000", 7 }, +}; + +START_PARAMETRIZED_TEST (test_decode_controls_len, decode_source_len) +{ + GString *actual_string; + + actual_string = decode_controls (data->input_string); + mctest_assert_mem_eq (actual_string->str, actual_string->len, data->expected_string, + data->expected_string_len); + g_string_free (actual_string, TRUE); +} +END_PARAMETRIZED_TEST + +/* --------------------------------------------------------------------------------------------- */ + int main (void) { @@ -123,6 +219,9 @@ main (void) tcase_add_test (tc_core, test_parse_csi); tcase_add_test (tc_core, test_strip_ctrl_codes); tcase_add_test (tc_core, test_strip_ctrl_codes2); + mctest_add_parameterized_test (tc_core, test_encode_controls, encode_source); + mctest_add_parameterized_test (tc_core, test_decode_controls, decode_source); + mctest_add_parameterized_test (tc_core, test_decode_controls_len, decode_source_len); // *********************************** return mctest_run_all (tc_core); diff --git a/tests/mctest.h b/tests/mctest.h index 80cb54fe3e..16acd5521c 100644 --- a/tests/mctest.h +++ b/tests/mctest.h @@ -19,6 +19,11 @@ g_assert_cmpstr (actual_result, ==, etalon_result); \ } +#define mctest_assert_mem_eq(actual_mem, actual_len, etalon_mem, etalon_len) \ + { \ + g_assert_cmpmem (actual_mem, actual_len, etalon_mem, etalon_len); \ + } + #define mctest_assert_ptr_eq(actual_pointer, etalon_pointer) \ { \ ck_assert_msg (actual_pointer == etalon_pointer, \