From 652f9ccf8249d3f828b8c177fe5771fe0a97955b Mon Sep 17 00:00:00 2001 From: Egmont Koblinger Date: Wed, 11 Mar 2026 09:57:20 +0100 Subject: [PATCH 1/7] Move a method to mc_config Move the method to get a config file's full name from keymap to mcconfig, because in the next commit keydef will need the same method. Signed-off-by: Egmont Koblinger --- lib/mcconfig.h | 3 ++ lib/mcconfig/paths.c | 69 ++++++++++++++++++++++++++++++++++++++++ src/keymap.c | 76 +++----------------------------------------- 3 files changed, 76 insertions(+), 72 deletions(-) diff --git a/lib/mcconfig.h b/lib/mcconfig.h index e73f166c97..614851d7bc 100644 --- a/lib/mcconfig.h +++ b/lib/mcconfig.h @@ -96,6 +96,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/paths.c b/lib/mcconfig/paths.c index 5f130573b6..c820877a67 100644 --- a/lib/mcconfig/paths.c +++ b/lib/mcconfig/paths.c @@ -308,3 +308,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/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) { From 91665ca562b1ce6d355dd55b9a8ffe2c4bd7c5ac Mon Sep 17 00:00:00 2001 From: Egmont Koblinger Date: Tue, 10 Mar 2026 13:03:37 +0100 Subject: [PATCH 2/7] Ticket #5047: Move the escape sequence definitions to a separate file Move the escape sequences from the global "mc.lib" and user "ini" file to new global and user "mc.keydef". Add command line option --keydef/--nokeydef and environment variable MC_KEYDEF, analogously to the keymap feature. Signed-off-by: Egmont Koblinger --- lib/fileloc.h | 1 + lib/mcconfig/paths.c | 1 + misc/Makefile.am | 1 + misc/mc.keydef | 161 ++++++++++++++++++++++++++++++++++++++++++ misc/mc.lib | 162 ------------------------------------------- src/args.c | 26 +++++++ src/args.h | 2 + src/learn.c | 22 +++--- src/main.c | 2 +- src/setup.c | 96 ++++++++++++++++++++----- src/setup.h | 2 +- 11 files changed, 287 insertions(+), 189 deletions(-) create mode 100644 misc/mc.keydef 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/paths.c b/lib/mcconfig/paths.c index c820877a67..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 }, 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..131d222c7e --- /dev/null +++ b/misc/mc.keydef @@ -0,0 +1,161 @@ +[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/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/learn.c b/src/learn.c index 86b93f849a..458f74284e 100644 --- a/src/learn.c +++ b/src/learn.c @@ -33,6 +33,7 @@ #include #include "lib/global.h" +#include "lib/fileloc.h" #include "lib/tty/tty.h" #include "lib/tty/key.h" @@ -360,9 +361,16 @@ static void learn_save (void) { int i; + mc_config_t *keydef_config; + char *fname; char *section; gboolean profile_changed = FALSE; + 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); + section = g_strconcat ("terminal:", getenv ("TERM"), (char *) NULL); for (i = 0; i < learn_total; i++) @@ -371,23 +379,19 @@ learn_save (void) 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); + mc_config_set_string_raw_value (keydef_config, section, key_name_conv_tab[i].name, + esc_str); g_free (esc_str); 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 (fname); g_free (section); + 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..a8a1051cf4 100644 --- a/src/setup.c +++ b/src/setup.c @@ -612,7 +612,7 @@ 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; @@ -630,7 +630,7 @@ load_keys_from_section (const char *terminal, mc_config_t *cfg) if (g_ascii_strcasecmp (*profile_keys, "copy") == 0) { valcopy = mc_config_get_string (cfg, section_name, *profile_keys, ""); - load_keys_from_section (valcopy, cfg); + load_keydefs_from_section (valcopy, cfg); g_free (valcopy); continue; } @@ -671,6 +671,25 @@ load_keys_from_section (const char *terminal, mc_config_t *cfg) /* --------------------------------------------------------------------------------------------- */ +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 ("general", keydef_config); + load_keydefs_from_section (getenv ("TERM"), keydef_config); + + mc_config_deinit (keydef_config); +} + +/* --------------------------------------------------------------------------------------------- */ + static void panel_save_type (const char *section, panel_view_mode_t type) { @@ -1086,24 +1105,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_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_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); + + // 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 From 64bca292390fbca1a6b581205c045c253d6a9079 Mon Sep 17 00:00:00 2001 From: Egmont Koblinger Date: Tue, 10 Mar 2026 13:06:35 +0100 Subject: [PATCH 3/7] Fix duplicate keys in mc.keydef Repeated keys aren't supported. Signed-off-by: Egmont Koblinger --- misc/mc.keydef | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/misc/mc.keydef b/misc/mc.keydef index 131d222c7e..76e831ff90 100644 --- a/misc/mc.keydef +++ b/misc/mc.keydef @@ -61,14 +61,10 @@ 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 +up=\\e[A;\\eOA +down=\\e[B;\\eOB +right=\\e[C;\\eOC +left=\\e[D;\\eOD [terminal:alacritty] copy=xterm From 336c2fccc32afd9ef7b0a5bfd323d0de8c439dbc Mon Sep 17 00:00:00 2001 From: Egmont Koblinger Date: Tue, 10 Mar 2026 13:08:06 +0100 Subject: [PATCH 4/7] Ticket #5048: Change the escaping format of mc.keydef Separate entries by space rather than semicolon. Semicolons are no longer escaped. The escape character is represented by \e, \E or ^[, rather than \\e. Other special characters also have more standard notation, e.g. ^H and ^?. Change the internal variables to hold the raw value rather than the escaped string. Signed-off-by: Egmont Koblinger --- lib/mcconfig.h | 4 + lib/mcconfig/get.c | 48 ++++++++ lib/mcconfig/set.c | 46 ++++++++ lib/terminal.c | 147 ++++++++++++++++++------ lib/terminal.h | 6 +- lib/tty/key.c | 22 +--- misc/mc.keydef | 268 +++++++++++++++++++++++++------------------ src/learn.c | 28 +++-- src/setup.c | 37 +++--- tests/lib/terminal.c | 99 ++++++++++++++++ tests/mctest.h | 5 + 11 files changed, 517 insertions(+), 193 deletions(-) diff --git a/lib/mcconfig.h b/lib/mcconfig.h index 614851d7bc..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: */ 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/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..1f0ed45e98 100644 --- a/lib/tty/key.c +++ b/lib/tty/key.c @@ -1169,24 +1169,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) { @@ -2124,7 +2106,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,7 +2130,7 @@ learn_key (void) } if (c == -1) break; - learn_store_key (buffer, c); + g_string_append_c (buffer, c); } tty_keypad (TRUE); tty_nodelay (FALSE); diff --git a/misc/mc.keydef b/misc/mc.keydef index 76e831ff90..4c141e9c6f 100644 --- a/misc/mc.keydef +++ b/misc/mc.keydef @@ -1,157 +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 + [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 +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 +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~ +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 +up = \e[A \eOA +down = \e[B \eOB +right = \e[C \eOC +left = \e[D \eOD [terminal:alacritty] -copy=xterm +copy = xterm [terminal:gnome] -copy=xterm +copy = xterm [terminal:rxvt] -copy=xterm +copy = xterm [terminal:xterm-new] -copy=xterm +copy = xterm [terminal:xterm-clear] -copy=xterm +copy = xterm [terminal:xterm-color] -copy=xterm +copy = xterm [terminal:xterm-256color] -copy=xterm +copy = xterm [terminal:xterm-direct] -copy=xterm +copy = xterm [terminal:xterm-direct16] -copy=xterm +copy = xterm [terminal:xterm-direct256] -copy=xterm +copy = xterm [terminal:screen] -copy=xterm +copy = xterm [terminal:screen-256color] -copy=xterm +copy = xterm [terminal:tmux] -copy=xterm +copy = xterm [terminal:tmux-256color] -copy=xterm +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[* +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 +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 +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 +copy = st [terminal:st-git-256color] -copy=st +copy = st [terminal:xterm-kitty] -copy=xterm +copy = xterm diff --git a/src/learn.c b/src/learn.c index 458f74284e..15e7875f41 100644 --- a/src/learn.c +++ b/src/learn.c @@ -39,7 +39,7 @@ #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" @@ -82,6 +82,14 @@ static gboolean learnchanged = FALSE; /*** file scope functions ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ +static void +learn_gstring_free (gpointer data) +{ + g_string_free ((GString *) data, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ + static int learn_button (WButton *button, int action) { @@ -114,8 +122,7 @@ learn_button (WButton *button, int action) && strcmp (seq, "^i") != 0 && (seq[1] != '\0' || *seq < ' ' || *seq > '~')) { learnchanged = TRUE; - learnkeys[action - B_USER].sequence = seq; - seq = convert_controls (seq); + learnkeys[action - B_USER].sequence = g_strdup (seq); seq_ok = define_sequence (key_name_conv_tab[action - B_USER].code, seq, MCKEY_NOACTION); } @@ -364,6 +371,7 @@ learn_save (void) mc_config_t *keydef_config; char *fname; char *section; + GPtrArray *list; gboolean profile_changed = FALSE; fname = mc_config_get_full_path (GLOBAL_KEYDEF_FILE); @@ -372,23 +380,25 @@ learn_save (void) mc_config_read_file (keydef_config, fname, FALSE, TRUE); section = g_strconcat ("terminal:", getenv ("TERM"), (char *) NULL); + list = g_ptr_array_new_with_free_func (learn_gstring_free); 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 (keydef_config, section, key_name_conv_tab[i].name, - esc_str); - g_free (esc_str); + GString *sequence_string; + sequence_string = g_string_new (learnkeys[i].sequence); + g_ptr_array_add (list, sequence_string); + mc_config_set_escape_sequence_list (keydef_config, section, key_name_conv_tab[i].name, + list); + g_ptr_array_remove_index_fast (list, 0); profile_changed = TRUE; } if (profile_changed) mc_config_save_file (keydef_config, NULL); + g_ptr_array_free (list, TRUE); g_free (fname); g_free (section); mc_config_deinit (keydef_config); diff --git a/src/setup.c b/src/setup.c index a8a1051cf4..6301d18b00 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) { @@ -616,7 +624,7 @@ 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; @@ -639,29 +647,22 @@ load_keydefs_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, section_name, *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, 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); } } } 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, \ From a9fe3e3bb2698f953da70cda7ccb63268832d784 Mon Sep 17 00:00:00 2001 From: Egmont Koblinger Date: Wed, 11 Mar 2026 21:20:42 +0100 Subject: [PATCH 5/7] Store escape sequences in GString This makes us one step closer to supporting escape sequences with an embedded NUL byte, although we probably won't need this. Signed-off-by: Egmont Koblinger --- lib/tty/key.c | 46 +++++++++++++++++++++++++++++++-------------- lib/tty/key.h | 4 ++-- lib/tty/keyxdef.c | 2 +- lib/tty/mouse.c | 4 ++-- lib/tty/tty-slang.c | 2 +- src/learn.c | 43 +++++++++++++++++++----------------------- src/setup.c | 3 ++- 7 files changed, 59 insertions(+), 45 deletions(-) diff --git a/lib/tty/key.c b/lib/tty/key.c index 1f0ed45e98..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); } /* --------------------------------------------------------------------------------------------- */ @@ -1584,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. @@ -1612,6 +1624,7 @@ define_sequence (int code, const char *seq, int action) base = base->child; seq++; + len--; } else { @@ -1619,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; } @@ -2089,7 +2102,8 @@ tty_getch (void) /* --------------------------------------------------------------------------------------------- */ -char * +/* Returned string might contain embedded NUL characters. */ +GString * learn_key (void) { // LEARN_TIMEOUT in ms @@ -2135,7 +2149,11 @@ learn_key (void) 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/src/learn.c b/src/learn.c index 15e7875f41..cd3ecb9978 100644 --- a/src/learn.c +++ b/src/learn.c @@ -63,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) *************************************************/ @@ -82,19 +82,11 @@ static gboolean learnchanged = FALSE; /*** file scope functions ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ -static void -learn_gstring_free (gpointer data) -{ - g_string_free ((GString *) data, TRUE); -} - -/* --------------------------------------------------------------------------------------------- */ - static int learn_button (WButton *button, int action) { WDialog *d; - char *seq; + GString *seq; (void) button; @@ -118,19 +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 > '~')) { - learnchanged = TRUE; - learnkeys[action - B_USER].sequence = g_strdup (seq); - seq_ok = define_sequence (key_name_conv_tab[action - B_USER].code, seq, MCKEY_NOACTION); + seq_ok = define_sequence (key_name_conv_tab[action - B_USER].code, seq->str, seq->len, + MCKEY_NOACTION); } - if (!seq_ok) + if (seq_ok) + { + learnchanged = TRUE; + learnkeys[action - B_USER].sequence = seq; + } + 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); @@ -380,15 +378,12 @@ learn_save (void) mc_config_read_file (keydef_config, fname, FALSE, TRUE); section = g_strconcat ("terminal:", getenv ("TERM"), (char *) NULL); - list = g_ptr_array_new_with_free_func (learn_gstring_free); + list = g_ptr_array_new (); for (i = 0; i < learn_total; i++) if (learnkeys[i].sequence != NULL) { - GString *sequence_string; - - sequence_string = g_string_new (learnkeys[i].sequence); - g_ptr_array_add (list, sequence_string); + g_ptr_array_add (list, learnkeys[i].sequence); mc_config_set_escape_sequence_list (keydef_config, section, key_name_conv_tab[i].name, list); g_ptr_array_remove_index_fast (list, 0); diff --git a/src/setup.c b/src/setup.c index 6301d18b00..a1a32a098c 100644 --- a/src/setup.c +++ b/src/setup.c @@ -659,7 +659,8 @@ load_keydefs_from_section (const char *terminal, mc_config_t *cfg) const GString *curr_value = (GString *) g_ptr_array_index (values, i); if (curr_value->len != 0) - define_sequence (key_code, curr_value->str, MCKEY_NOACTION); + define_sequence (key_code, curr_value->str, curr_value->len, + MCKEY_NOACTION); } g_ptr_array_free (values, TRUE); From acf366520604b3efb9bf8d9b7c9529d9ad2cf864 Mon Sep 17 00:00:00 2001 From: Egmont Koblinger Date: Tue, 10 Mar 2026 13:12:24 +0100 Subject: [PATCH 6/7] Ticket #5047: Drop the "terminal:" prefix from mc.keydef section names Also rename [general] to [_common_], emphasizing that it's not a terminal name and that it's always loaded. Signed-off-by: Egmont Koblinger --- misc/mc.keydef | 46 +++++++++++++++++++++++----------------------- src/learn.c | 7 ++----- src/setup.c | 13 +++++-------- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/misc/mc.keydef b/misc/mc.keydef index 4c141e9c6f..5e045bb01b 100644 --- a/misc/mc.keydef +++ b/misc/mc.keydef @@ -46,7 +46,7 @@ # # tput rmkx -[terminal:console] +[console] insert = \e[2~ f11 = \e[23~ f12 = \e[24~ @@ -75,10 +75,10 @@ kpenter = \eOM kpslash = \eOQ kpnumlock = \eOP -[terminal:linux] +[linux] copy = console -[terminal:xterm] +[xterm] insert = \e[2~ f11 = \e[23~ \eO2P \eO1;2P \e[1;2P f12 = \e[24~ \eO2Q \eO1;2Q \e[1;2Q @@ -114,49 +114,49 @@ down = \e[B \eOB right = \e[C \eOC left = \e[D \eOD -[terminal:alacritty] +[alacritty] copy = xterm -[terminal:gnome] +[gnome] copy = xterm -[terminal:rxvt] +[rxvt] copy = xterm -[terminal:xterm-new] +[xterm-new] copy = xterm -[terminal:xterm-clear] +[xterm-clear] copy = xterm -[terminal:xterm-color] +[xterm-color] copy = xterm -[terminal:xterm-256color] +[xterm-256color] copy = xterm -[terminal:xterm-direct] +[xterm-direct] copy = xterm -[terminal:xterm-direct16] +[xterm-direct16] copy = xterm -[terminal:xterm-direct256] +[xterm-direct256] copy = xterm -[terminal:screen] +[screen] copy = xterm -[terminal:screen-256color] +[screen-256color] copy = xterm -[terminal:tmux] +[tmux] copy = xterm -[terminal:tmux-256color] +[tmux-256color] copy = xterm -[terminal:ibmpc3] +[ibmpc3] f11 = \e[Y f12 = \e[Z f13 = \e[a @@ -173,7 +173,7 @@ kpplus = \e[+ kpminus = \e[- kpasterisk = \e[* -[terminal:cons25] +[cons25] f11 = \e[Y f12 = \e[Z f13 = \e[a @@ -185,7 +185,7 @@ f18 = \e[f f19 = \e[g f20 = \e[h -[terminal:st] +[st] shift-right = \e[1;2C shift-left = \e[1;2D shift-up = \e[1;2A @@ -195,11 +195,11 @@ alt-shift-left = \e[1;4D alt-shift-up = \e[1;4A alt-shift-down = \e[1;4B -[terminal:st-256color] +[st-256color] copy = st -[terminal:st-git-256color] +[st-git-256color] copy = st -[terminal:xterm-kitty] +[xterm-kitty] copy = xterm diff --git a/src/learn.c b/src/learn.c index cd3ecb9978..f544b4c6fc 100644 --- a/src/learn.c +++ b/src/learn.c @@ -368,7 +368,6 @@ learn_save (void) int i; mc_config_t *keydef_config; char *fname; - char *section; GPtrArray *list; gboolean profile_changed = FALSE; @@ -377,15 +376,14 @@ learn_save (void) if (exist_file (fname)) mc_config_read_file (keydef_config, fname, FALSE, TRUE); - section = g_strconcat ("terminal:", getenv ("TERM"), (char *) NULL); list = g_ptr_array_new (); for (i = 0; i < learn_total; i++) if (learnkeys[i].sequence != NULL) { g_ptr_array_add (list, learnkeys[i].sequence); - mc_config_set_escape_sequence_list (keydef_config, section, key_name_conv_tab[i].name, - list); + 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; } @@ -395,7 +393,6 @@ learn_save (void) g_ptr_array_free (list, TRUE); g_free (fname); - g_free (section); mc_config_deinit (keydef_config); } diff --git a/src/setup.c b/src/setup.c index a1a32a098c..56167036e2 100644 --- a/src/setup.c +++ b/src/setup.c @@ -622,22 +622,20 @@ load_layout (void) static void load_keydefs_from_section (const char *terminal, mc_config_t *cfg) { - char *section_name; gchar **profile_keys, **keys; 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, ""); + valcopy = mc_config_get_string (cfg, terminal, *profile_keys, ""); load_keydefs_from_section (valcopy, cfg); g_free (valcopy); continue; @@ -649,7 +647,7 @@ load_keydefs_from_section (const char *terminal, mc_config_t *cfg) { GPtrArray *values; - values = mc_config_get_escape_sequence_list (cfg, section_name, *profile_keys); + values = mc_config_get_escape_sequence_list (cfg, terminal, *profile_keys); if (values != NULL) { g_ptr_array_set_free_func (values, setup__gstring_free); @@ -668,7 +666,6 @@ load_keydefs_from_section (const char *terminal, mc_config_t *cfg) } } g_strfreev (keys); - g_free (section_name); } /* --------------------------------------------------------------------------------------------- */ @@ -684,7 +681,7 @@ load_keydefs_from_file (const char *fname) keydef_config = mc_config_init (NULL, TRUE); mc_config_read_file (keydef_config, fname, TRUE, TRUE); - load_keydefs_from_section ("general", keydef_config); + load_keydefs_from_section ("_common_", keydef_config); load_keydefs_from_section (getenv ("TERM"), keydef_config); mc_config_deinit (keydef_config); From 5cf40267c50760a6477b4d96f069207b40806cd4 Mon Sep 17 00:00:00 2001 From: Egmont Koblinger Date: Tue, 24 Mar 2026 12:34:37 +0100 Subject: [PATCH 7/7] man: Update for new mc.keydef locations and section names Signed-off-by: Egmont Koblinger --- doc/man/mc.1.in | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) 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.