From 2f08ad2f9f90398a83055807cf8cfdb1093ed319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hork=C3=BD?= Date: Sat, 15 Nov 2025 18:23:52 +0100 Subject: [PATCH 1/6] .gitignore: Ignore lib/stdckdint.h, *.log, and *.orig MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ignore lib/stdckdint.h, which is generated by autoconf, *.log, which are generated by make check, and *.orig, which are generated by git mergetool. Signed-off-by: Jakub Horký --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 6d8cb8b62..48d6748b3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ *.la *~ *.cache +*.log +*.orig *.tar.gz # autoconf runs "mktemp ./confXXXXXX" /conf??????/ @@ -44,6 +46,7 @@ TAGS doc/devel/ doc/html/ .deps +lib/stdckdint.h libtool make.log make.clang From d057c0b279846a94055929a5bc5405959aa1b986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hork=C3=BD?= Date: Fri, 7 Nov 2025 19:17:17 +0100 Subject: [PATCH 2/6] lib/tty: Add tty_putp() and tty_tiparm() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add standard interfaces for outputting escape sequences and for applying parameters to parametrized terminfo capabilities. Support is added for both ncurses and S-Lang, but S-Lang supports a maximum of two parameters for any parametrized capability. Signed-off-by: Jakub Horký --- lib/tty/tty-ncurses.c | 9 +++++++++ lib/tty/tty-slang.c | 27 +++++++++++++++++++++++++++ lib/tty/tty.h | 10 ++++++++-- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/tty/tty-ncurses.c b/lib/tty/tty-ncurses.c index 96c24707d..ebdfc2a04 100644 --- a/lib/tty/tty-ncurses.c +++ b/lib/tty/tty-ncurses.c @@ -723,6 +723,15 @@ tty_print_string (const char *s) /* --------------------------------------------------------------------------------------------- */ +void +tty_putp (const char *s) +{ + putp (s); + fflush (stdout); +} + +/* --------------------------------------------------------------------------------------------- */ + void tty_printf (const char *fmt, ...) { diff --git a/lib/tty/tty-slang.c b/lib/tty/tty-slang.c index e70b26354..f148a13e2 100644 --- a/lib/tty/tty-slang.c +++ b/lib/tty/tty-slang.c @@ -679,6 +679,16 @@ tty_print_string (const char *s) /* --------------------------------------------------------------------------------------------- */ +void +tty_putp (const char *s) +{ + // S-Lang has SLtt_tputs(), but it only passes the chars through one-by-one without any + // processing + SLtt_write_string ((char *) s); +} + +/* --------------------------------------------------------------------------------------------- */ + void tty_printf (const char *fmt, ...) { @@ -723,6 +733,23 @@ tty_tigetstr (const char *terminfo_cap, const char *termcap_cap) /* --------------------------------------------------------------------------------------------- */ +// Warning: S-Lang doesn't support more than two parameters +char * +tty_tiparm (const char *str, ...) +{ + va_list args; + int p1, p2; + + va_start (args, str); + p1 = va_arg (args, int); + p2 = va_arg (args, int); + va_end (args); + + return SLtt_tgoto ((SLFUTURE_CONST char *) str, p2, p1); +} + +/* --------------------------------------------------------------------------------------------- */ + void tty_refresh (void) { diff --git a/lib/tty/tty.h b/lib/tty/tty.h index 48c055daf..242042683 100644 --- a/lib/tty/tty.h +++ b/lib/tty/tty.h @@ -92,11 +92,16 @@ typedef enum // the values are either one of MCS_ACS_* or a character in the current locale. extern mc_tty_char_t mc_tty_frm[]; +/*** declarations of public functions ************************************************************/ + extern int tty_tigetflag (const char *terminfo_cap, const char *termcap_cap); extern int tty_tigetnum (const char *terminfo_cap, const char *termcap_cap); extern char *tty_tigetstr (const char *terminfo_cap, const char *termcap_cap); - -/*** declarations of public functions ************************************************************/ +#ifdef HAVE_SLANG +extern char *tty_tiparm (const char *str, ...); +#else +#define tty_tiparm(str, ...) tiparm ((NCURSES_CONST char *) (str), __VA_ARGS__) +#endif extern void tty_beep (void); @@ -146,6 +151,7 @@ extern void tty_print_char (mc_tty_char_t c); extern void tty_print_anychar (mc_tty_char_t c); extern void tty_print_string (const char *s); extern void tty_printf (const char *s, ...) G_GNUC_PRINTF (1, 2); +extern void tty_putp (const char *s); extern void tty_print_one_vline (gboolean single); extern void tty_print_one_hline (gboolean single); From 34a216a584805c1d30efff54ff92ade4086b5f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hork=C3=BD?= Date: Sat, 15 Nov 2025 20:19:17 +0100 Subject: [PATCH 3/6] lib/tty/key.c: Replace paranoia code with parsing code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old functionality that "eats" all characters following an unknown escape sequence is removed, as it causes severe side effects (such as not responding to any keys following an unknown sequence for some time). This is superseded by parsing CSI and SS3 sequences, which are the de facto standard for sending any function keys. Resolves: #3136 Signed-off-by: Jakub Horký --- lib/tty/key.c | 72 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/lib/tty/key.c b/lib/tty/key.c index 62a0f1095..037e6a05e 100644 --- a/lib/tty/key.c +++ b/lib/tty/key.c @@ -221,8 +221,8 @@ const key_code_name_t key_name_conv_tab[] = { #define MC_USEC_PER_MSEC 1000 -/* The maximum sequence length (32 + null terminator) */ -#define SEQ_BUFFER_LEN 33 +/* The maximum sequence length */ +#define SEQ_BUFFER_LEN 100 /*** file scope type declarations ****************************************************************/ @@ -283,6 +283,24 @@ static key_define_t mc_default_keys[] = { { ESC_CHAR, ESC_STR ESC_STR, MCKEY_NOACTION }, { MCKEY_BRACKETED_PASTING_START, ESC_STR "[200~", MCKEY_NOACTION }, { MCKEY_BRACKETED_PASTING_END, ESC_STR "[201~", MCKEY_NOACTION }, + + // Linux function keys F1-F5 + // They must be defined here because the generic CSI handler in get_key_code() can't ignore them + // quietly, as they violate standardized CSI syntax + { KEY_F (1), ESC_STR "[[A", MCKEY_NOACTION }, + { KEY_F (2), ESC_STR "[[B", MCKEY_NOACTION }, + { KEY_F (3), ESC_STR "[[C", MCKEY_NOACTION }, + { KEY_F (4), ESC_STR "[[D", MCKEY_NOACTION }, + { KEY_F (5), ESC_STR "[[E", MCKEY_NOACTION }, + + // DEC VT100 cursor application mode keys + // Since there must be at least one SS3 definition for the get_key_code() sequence parser to + // work correctly, define these cursor sequences as they are very common + { KEY_UP, ESC_STR "OA", MCKEY_NOACTION }, + { KEY_DOWN, ESC_STR "OB", MCKEY_NOACTION }, + { KEY_RIGHT, ESC_STR "OC", MCKEY_NOACTION }, + { KEY_LEFT, ESC_STR "OD", MCKEY_NOACTION }, + { 0, NULL, MCKEY_NOACTION }, }; @@ -1716,31 +1734,45 @@ get_key_code (int no_delay) pend_send: if (pending_keys != NULL) { - gboolean bad_seq; + /* At this point, no sequence was found in the keys tree. + * + * Now discard all CSI and SS3 sequences, as these two are the de-facto standard for + * sending key sequences in most terminals. The only well-known deviations are: + * - Linux console (it sends invalid CSI for F1-F5, as double '[' is not allowed in CSI); + * this is handled by defining them in our key defines table (see above). + * - xterm with modify*Keys:0 (the old behavior, which sends parametrized SS3 sequences), + * and many terminals that inherited this ill behavior, as SS3 can't have any parameters. + * As a workaround, we treat SS3 like CSI. + * - Some terminals begin sequences with ESC ESC to indicate Alt modifier (for example + * PuTTY). + */ + + if (pending_keys[0] == ESC_CHAR && pending_keys[1] == ESC_CHAR) + pending_keys++; + + if (pending_keys[0] == ESC_CHAR && (pending_keys[1] == '[' || pending_keys[1] == 'O')) + { + c = seq_append[-1]; + + // Get all parameter bytes and intermediate bytes before the final byte + while (c >= ' ' && c <= '?') + c = tty_lowlevel_getch (); + + pending_keys = seq_append = NULL; + + return -1; + } + + // Most terminals indicate the Alt key by prepending to the character sent c = *pending_keys++; while (c == ESC_CHAR) c = ALT (*pending_keys++); - bad_seq = (*pending_keys != ESC_CHAR && *pending_keys != '\0'); - if (*pending_keys == '\0' || bad_seq) + if (*pending_keys == '\0') pending_keys = seq_append = NULL; - if (bad_seq) - { - /* This is an unknown ESC sequence. - * To prevent interpreting its tail as a random garbage, - * eat and discard all buffered and quickly following chars. - * Small, but non-zero timeout is needed to reconnect - * escape sequence split up by e.g. a serial line. - */ - int paranoia = 20; - - while (getch_with_timeout (old_esc_mode_timeout) >= 0 && --paranoia != 0) - ; - } - else - goto done; + goto done; } nodelay_try_again: From 5097399410fdf99208fa0f1d2e9b80c1ecf75f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hork=C3=BD?= Date: Thu, 23 Oct 2025 00:26:19 +0200 Subject: [PATCH 4/6] lib/tty/key.c: Fix typo in comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakub Horký --- lib/tty/key.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tty/key.c b/lib/tty/key.c index 037e6a05e..f71421acb 100644 --- a/lib/tty/key.c +++ b/lib/tty/key.c @@ -1082,7 +1082,7 @@ correct_key_code (int code) */ if (c == '\b') { - // Special case for backspase ('\b' < 32) + // Special case for backspace ('\b' < 32) c = KEY_BACKSPACE; mod &= ~KEY_M_CTRL; } From 06d5543cbebbca80426042b9c3d5a2cb2a79e5f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hork=C3=BD?= Date: Thu, 23 Oct 2025 18:27:12 +0200 Subject: [PATCH 5/6] tests/lib/tty.c: Add key mocking support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make functions tty_lowlevel_getch() and getch_with_timeout() mockable by defining them as weak. Fake key input can be passed by mock_input(). Signed-off-by: Jakub Horký --- lib/tty/key.c | 40 +++++++++---------- lib/tty/key.h | 1 + lib/tty/tty-internal.h | 4 +- lib/tty/tty-slang.c | 4 +- tests/lib/Makefile.am | 1 + tests/lib/tty.c | 90 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 22 deletions(-) diff --git a/lib/tty/key.c b/lib/tty/key.c index f71421acb..376e35de4 100644 --- a/lib/tty/key.c +++ b/lib/tty/key.c @@ -1167,26 +1167,6 @@ correct_key_code (int code) /* --------------------------------------------------------------------------------------------- */ -static int -getch_with_timeout (unsigned int delay_us) -{ - fd_set Read_FD_Set; - int c; - struct timeval time_out; - - time_out.tv_sec = delay_us / G_USEC_PER_SEC; - time_out.tv_usec = delay_us % G_USEC_PER_SEC; - tty_nodelay (TRUE); - FD_ZERO (&Read_FD_Set); - FD_SET (input_fd, &Read_FD_Set); - select (input_fd + 1, &Read_FD_Set, NULL, NULL, &time_out); - c = tty_lowlevel_getch (); - tty_nodelay (FALSE); - return c; -} - -/* --------------------------------------------------------------------------------------------- */ - static void learn_store_key (GString *buffer, int c) { @@ -2139,6 +2119,26 @@ tty_getch (void) /* --------------------------------------------------------------------------------------------- */ +int +getch_with_timeout (unsigned int delay_us) +{ + fd_set Read_FD_Set; + int c; + struct timeval time_out; + + time_out.tv_sec = delay_us / G_USEC_PER_SEC; + time_out.tv_usec = delay_us % G_USEC_PER_SEC; + tty_nodelay (TRUE); + FD_ZERO (&Read_FD_Set); + FD_SET (input_fd, &Read_FD_Set); + select (input_fd + 1, &Read_FD_Set, NULL, NULL, &time_out); + c = tty_lowlevel_getch (); + tty_nodelay (FALSE); + return c; +} + +/* --------------------------------------------------------------------------------------------- */ + char * learn_key (void) { diff --git a/lib/tty/key.h b/lib/tty/key.h index 3d902678e..87691cec8 100644 --- a/lib/tty/key.h +++ b/lib/tty/key.h @@ -80,6 +80,7 @@ char *tty_keycode_to_keyname (const int keycode); int tty_get_event (struct Gpm_Event *event, gboolean redo_event, gboolean block); gboolean is_idle (void); int tty_getch (void); +MC_MOCKABLE int getch_with_timeout (unsigned int delay_us); /* While waiting for input, the program can select on more than one file */ typedef int (*select_fn) (int fd, void *info); diff --git a/lib/tty/tty-internal.h b/lib/tty/tty-internal.h index c7a89e7ab..af6bda4b2 100644 --- a/lib/tty/tty-internal.h +++ b/lib/tty/tty-internal.h @@ -35,12 +35,14 @@ extern int sigwinch_pipe[2]; /*** declarations of public functions ************************************************************/ +void load_terminfo_keys (void); + void tty_create_winch_pipe (void); void tty_destroy_winch_pipe (void); char *mc_tty_normalize_from_utf8 (const char *str); void tty_init_xterm_support (gboolean is_xterm); -int tty_lowlevel_getch (void); +MC_MOCKABLE int tty_lowlevel_getch (void); void tty_colorize_area (int y, int x, int rows, int cols, int color); diff --git a/lib/tty/tty-slang.c b/lib/tty/tty-slang.c index f148a13e2..632a88b97 100644 --- a/lib/tty/tty-slang.c +++ b/lib/tty/tty-slang.c @@ -210,9 +210,11 @@ do_define_key (int code, const char *strcap) define_sequence (code, seq, MCKEY_NOACTION); } +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ /* --------------------------------------------------------------------------------------------- */ -static void +void load_terminfo_keys (void) { int i; diff --git a/tests/lib/Makefile.am b/tests/lib/Makefile.am index e66688dbb..fc2ed2b26 100644 --- a/tests/lib/Makefile.am +++ b/tests/lib/Makefile.am @@ -51,6 +51,7 @@ terminal_SOURCES = \ tty_SOURCES = \ tty.c +tty_LDADD = $(SLANG_LIBS) utilunix__mc_pstream_get_string_SOURCES = \ utilunix__mc_pstream_get_string.c diff --git a/tests/lib/tty.c b/tests/lib/tty.c index a458a06ce..34e727b9f 100644 --- a/tests/lib/tty.c +++ b/tests/lib/tty.c @@ -30,6 +30,46 @@ #include "lib/util.h" #include "lib/tty/tty.h" +#include "lib/tty/tty-internal.h" +#include "lib/tty/key.h" + +static int mock_input_buf[1000]; +static int *mock_input_ptr; + +static void +mock_input (const char *charinput) +{ + mock_input_ptr = mock_input_buf; + + while (*charinput != '\0') + *mock_input_ptr++ = (*charinput++ & 0xFF); + *mock_input_ptr = '\0'; + + mock_input_ptr = mock_input_buf; +} + +/* @Mock */ +int +tty_lowlevel_getch (void) +{ + int key = *mock_input_ptr; + if (key != '\0') + { + mock_input_ptr++; + return key; + } + else + return -1; +} + +/* @Mock */ +int +getch_with_timeout (unsigned int delay_us) +{ + (void) (delay_us); + + return tty_lowlevel_getch (); +} /* --------------------------------------------------------------------------------------------- */ /* @CapturedValue */ @@ -74,6 +114,7 @@ START_TEST (test_tty_check_term_non_xterm) ck_assert_int_eq (actual_result_force_true, 1); } END_TEST + /* --------------------------------------------------------------------------------------------- */ START_TEST (test_tty_check_term_xterm_like) @@ -94,6 +135,54 @@ END_TEST /* --------------------------------------------------------------------------------------------- */ +START_TEST (test_tty_get_key_code) +{ + mc_global.tty.xterm_flag = TRUE; + + setenv ("TERM", "xterm", 1); + init_key (); +#ifdef HAVE_SLANG + SLtt_get_terminfo (); + load_terminfo_keys (); +#endif + + mock_input ("\x1b[1;2A"); + ck_assert_int_eq (get_key_code (0), KEY_M_SHIFT | KEY_UP); + ck_assert_int_eq (get_key_code (0), -1); + ck_assert_int_eq (get_key_code (0), -1); + + mock_input ("😊FG"); + ck_assert_int_eq (get_key_code (0), 0xF0); + ck_assert_int_eq (get_key_code (0), 0x9F); + ck_assert_int_eq (get_key_code (0), 0x98); + ck_assert_int_eq (get_key_code (0), 0x8A); + ck_assert_int_eq (get_key_code (0), 'F'); + ck_assert_int_eq (get_key_code (0), 'G'); + ck_assert_int_eq (get_key_code (0), -1); + ck_assert_int_eq (get_key_code (0), -1); + + mock_input ("ц\x1b[1;2A=5ů§a"); + ck_assert_int_eq (get_key_code (0), 0xD1); // 'ц' + ck_assert_int_eq (get_key_code (0), 0x86); + ck_assert_int_eq (get_key_code (0), KEY_M_SHIFT | KEY_UP); + ck_assert_int_eq (get_key_code (0), '='); + ck_assert_int_eq (get_key_code (0), '5'); + ck_assert_int_eq (get_key_code (0), 0xC5); // 'ů' + ck_assert_int_eq (get_key_code (0), 0xAF); + ck_assert_int_eq (get_key_code (0), 0xC2); // '§' + ck_assert_int_eq (get_key_code (0), 0xA7); + ck_assert_int_eq (get_key_code (0), 'a'); + ck_assert_int_eq (get_key_code (0), -1); + ck_assert_int_eq (get_key_code (0), -1); + + mock_input ("\x1b[u"); + ck_assert_int_eq (get_key_code (0), -1); + ck_assert_int_eq (get_key_code (0), -1); +} +END_TEST + +/* --------------------------------------------------------------------------------------------- */ + int main (void) { @@ -105,6 +194,7 @@ main (void) tcase_add_test (tc_core, test_tty_check_term_unset); tcase_add_test (tc_core, test_tty_check_term_non_xterm); tcase_add_test (tc_core, test_tty_check_term_xterm_like); + tcase_add_test (tc_core, test_tty_get_key_code); // *********************************** return mctest_run_all (tc_core); From 3eb6be846e9087757f0f6cfbc638eb616022e053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Hork=C3=BD?= Date: Sat, 15 Nov 2025 20:26:22 +0100 Subject: [PATCH 6/6] lib/tty/key.c: Add Kitty Keyboard Protocol support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The protocol is described here: https://sw.kovidgoyal.net/kitty/keyboard-protocol/ Check for kitty sequence when no entry is found in the keys tree, before the sequence is discarded. Resolves: #4762 Signed-off-by: Jakub Horký --- doc/man/es/mc.1.in | 7 + doc/man/hu/mc.1.in | 8 + doc/man/it/mc.1.in | 8 + doc/man/mc.1.in | 7 + doc/man/pl/mc.1.in | 7 + doc/man/ru/mc.1.in | 7 + doc/man/sr/mc.1.in | 8 + lib/global.c | 3 +- lib/global.h | 4 + lib/terminal.c | 1 + lib/terminal.h | 1 + lib/tty/key.c | 344 ++++++++++++++++++++++++++++++++---------- lib/tty/key.h | 5 +- lib/tty/tty-ncurses.c | 2 + lib/tty/tty-slang.c | 2 + lib/tty/tty.c | 20 +++ lib/tty/tty.h | 1 + misc/mc.lib | 41 +---- src/execute.c | 4 + src/setup.c | 1 + tests/lib/tty.c | 58 +++++++ 21 files changed, 417 insertions(+), 122 deletions(-) diff --git a/doc/man/es/mc.1.in b/doc/man/es/mc.1.in index b06798549..875f756e8 100644 --- a/doc/man/es/mc.1.in +++ b/doc/man/es/mc.1.in @@ -3965,6 +3965,13 @@ For example: .nf autodetect_codeset=russian .fi +.TP +.I kitty_keyboard_protocol +De forma predeterminada, Midnight Commander envía una secuencia de escape que habilita el +protocolo de teclado de Kitty, lo que permite un manejo preciso del teclado (incluyendo +combinaciones de teclas como C\-Enter o C\-S\-Enter) cuando se conecta desde terminales +que admiten este protocolo. Si se establece en falso, Midnight Commander no intentará +habilitar este protocolo. .\"NODE "Parameters for external editor or viewer" .SH "Parámetros para editor o visor externo" Midnight Commander permite especificar opciones para editores y visores diff --git a/doc/man/hu/mc.1.in b/doc/man/hu/mc.1.in index 07ec0ebc9..deda5cbc2 100644 --- a/doc/man/hu/mc.1.in +++ b/doc/man/hu/mc.1.in @@ -3014,6 +3014,14 @@ utolsó fájl a panelben. .IP Ha ez a változó be van állítva (alapértelmezésben) meg foja jelölni azt a fájl parancsot, amelyhez a +.PP +.I kitty_keyboard_protocol +Alapértelmezés szerint a Midnight Commander egy olyan escape szekvenciát +küld, amely engedélyezi a Kitty billentyűzetprotokollt. Ez lehetővé teszi +a billentyűzet pontos kezelését (beleértve az olyan billentyűkombinációkat +is, mint a C\-Enter vagy a C\-S\-Enter), amikor olyan terminálról +csatlakoznak, amely támogatja ezt a protokollt. Ha az érték hamisra van +állítva, a Midnight Commander nem próbálja meg engedélyezni ezt a protokollt. .\"LINK2" Társításokban .\"Edit Extension File" diff --git a/doc/man/it/mc.1.in b/doc/man/it/mc.1.in index db00bf961..790e356a2 100644 --- a/doc/man/it/mc.1.in +++ b/doc/man/it/mc.1.in @@ -3014,6 +3014,14 @@ pannello. .I use_file_to_guess_type Se questa variabile è abilitata (valore predefinito) userà il comando file per trovare delle corrispondenze sui tipi di file elencati nel +.TP +.I kitty_keyboard_protocol +Per impostazione predefinita, Midnight Commander invia una sequenza di +escape che abilita il Kitty Keyboard Protocol, il quale consente una +gestione precisa della tastiera (incluse combinazioni di tasti come +C\-Enter o C\-S\-Enter) quando ci si connette da terminali che supportano +questo protocollo. Se impostato su false, Midnight Commander non tenterà +di abilitare questo protocollo. .\"LINK2" file mc.ext.ini\&. .\"Edit Extension File" diff --git a/doc/man/mc.1.in b/doc/man/mc.1.in index 1ba004697..39eebcb6d 100644 --- a/doc/man/mc.1.in +++ b/doc/man/mc.1.in @@ -4208,6 +4208,13 @@ For example: .nf autodetect_codeset=russian .fi +.TP +.I kitty_keyboard_protocol +By default, Midnight Commander sends an escape sequence that enables the Kitty +Keyboard Protocol, which allows precise keyboard handling (including key +combinations such as C\-Enter or C\-S\-Enter) when connected from terminals that +support this protocol. If set to false, Midnight Commander will not attempt to +enable this protocol. .\"NODE "Parameters for external editor or viewer" .SH "Parameters for external editor or viewer" Midnight Commander provides a way for specify an options for external editors diff --git a/doc/man/pl/mc.1.in b/doc/man/pl/mc.1.in index e00e710f0..9ef338bf6 100644 --- a/doc/man/pl/mc.1.in +++ b/doc/man/pl/mc.1.in @@ -2708,6 +2708,13 @@ Jeśli ta opcja jest włączona (standardowo tak nie jest) kiedy przeglądasz pl w panelu drzewa, będzie on automatycznie przeładowywał drugi panel na zawartość wybranego katalogu. .PP +.I kitty_keyboard_protocol +Domyślnie Midnight Commander wysyła sekwencję escape, która włącza protokół +klawiatury Kitty. Umożliwia on precyzyjną obsługę klawiatury (w tym kombinacje +klawiszy takie jak C\-Enter czy C\-S\-Enter) podczas połączenia z terminalami +obsługującymi ten protokół. Jeśli ustawione na false, Midnight Commander +nie będzie próbował włączyć tego protokołu. +.PP .\"NODE "Terminal databases" .SH Baza danych terminali (Terminal databases) Midnight Commander pozwala ci na naprawienie bazy danych terminali bez diff --git a/doc/man/ru/mc.1.in b/doc/man/ru/mc.1.in index 758ac9144..07a2ef901 100644 --- a/doc/man/ru/mc.1.in +++ b/doc/man/ru/mc.1.in @@ -4663,6 +4663,13 @@ clipboard_paste=xclip \-o .nf autodetect_codeset=russian .fi +.PP +.I kitty_keyboard_protocol +По умолчанию Midnight Commander отправляет управляющую последовательность, которая +включает протокол клавиатуры Kitty. Этот протокол обеспечивает точную обработку +клавиатуры (включая такие комбинации клавиш, как C\-Enter или C\-S\-Enter) при +подключении из терминалов, поддерживающих данный протокол. Если установить значение +в false, Midnight Commander не будет пытаться включить этот протокол. .\"NODE "Parameters for external editor or viewer" .SH "Параметры для внешних редакторов и программ просмотра" Midnight Commander позволяет задать некоторые параметрыы для внешних редакторов diff --git a/doc/man/sr/mc.1.in b/doc/man/sr/mc.1.in index 8f0a41931..11c26d41d 100644 --- a/doc/man/sr/mc.1.in +++ b/doc/man/sr/mc.1.in @@ -4044,6 +4044,14 @@ clipboard_paste=xclip \-o .nf autodetect_codeset=russian .fi +.TP +.I kitty_keyboard_protocol +Подразумевано, Midnight Commander шаље escape секвенцу која омогућава +Kitty протокол тастатуре, што омогућава прецизно руковање тастатуром +(укључујући комбинације тастера као што су C\-Enter или C\-S\-Enter) +када се повезује са терминала који подржавају овај протокол. Ако је +постављено на false, Midnight Commander неће покушати да омогући овај +протокол. .\"NODE "Parameters for external editor or viewer" .SH "Параметри спољашњег уређивача или прегледача" Поноћни наредник обезбеђује начин да се задају опције за спољашње уређиваче и diff --git a/lib/global.c b/lib/global.c index 2fef6a5a9..ff536562c 100644 --- a/lib/global.c +++ b/lib/global.c @@ -103,7 +103,8 @@ mc_global_t mc_global = .disable_colors = FALSE, .ugly_line_drawing = FALSE, .old_mouse = FALSE, - .alternate_plus_minus = FALSE + .alternate_plus_minus = FALSE, + .kitty_keyboard_protocol = TRUE }, .vfs = diff --git a/lib/global.h b/lib/global.h index b8613f943..0059138cf 100644 --- a/lib/global.h +++ b/lib/global.h @@ -206,6 +206,10 @@ typedef struct /* If true, use + and \ keys normally and select/unselect do if M-+ / M-\. and M-- and keypad + / - */ gboolean alternate_plus_minus; + + // If true, send Kitty Keyboard Protocol initialization string to the terminal + // Defaults to true, because terminals that don't support it simply do nothing + gboolean kitty_keyboard_protocol; } tty; struct diff --git a/lib/terminal.c b/lib/terminal.c index a7e460130..e087e907d 100644 --- a/lib/terminal.c +++ b/lib/terminal.c @@ -156,6 +156,7 @@ parse_csi (csi_command_t *out, const char **sptr, const char *end) { out->private_mode = private_mode; out->param_count = param_count; + out->final_byte = c; } invalid_sequence: diff --git a/lib/terminal.h b/lib/terminal.h index a5e453635..d47b61956 100644 --- a/lib/terminal.h +++ b/lib/terminal.h @@ -21,6 +21,7 @@ typedef struct char private_mode; uint32_t params[16][4]; size_t param_count; + char final_byte; } csi_command_t; /*** global variables defined in .c file *********************************************************/ diff --git a/lib/tty/key.c b/lib/tty/key.c index 376e35de4..052fe0472 100644 --- a/lib/tty/key.c +++ b/lib/tty/key.c @@ -56,6 +56,7 @@ #include "tty-internal.h" // mouse_enabled #include "mouse.h" #include "key.h" +#include "lib/terminal.h" #include "lib/widget.h" // mc_refresh() @@ -253,6 +254,13 @@ typedef struct int action; } key_define_t; +typedef struct +{ + int code; + unsigned int kitty_code; + const char final_byte; +} key_kitty_t; + /* File descriptor monitoring add/remove routines */ typedef struct { @@ -306,20 +314,16 @@ static key_define_t mc_default_keys[] = { /* Broken terminfo and termcap databases on xterminals */ static key_define_t xterm_key_defines[] = { + + /* The Kitty Keyboard Protocol extends the most common key escape sequences in a backwards + * compatible way. Thus standard sequences are implicitly handled by the Kitty handler, and + * don't need to be listed here. + */ + { KEY_F (1), ESC_STR "OP", MCKEY_NOACTION }, { KEY_F (2), ESC_STR "OQ", MCKEY_NOACTION }, { KEY_F (3), ESC_STR "OR", MCKEY_NOACTION }, { KEY_F (4), ESC_STR "OS", MCKEY_NOACTION }, - { KEY_F (1), ESC_STR "[11~", MCKEY_NOACTION }, - { KEY_F (2), ESC_STR "[12~", MCKEY_NOACTION }, - { KEY_F (3), ESC_STR "[13~", MCKEY_NOACTION }, - { KEY_F (4), ESC_STR "[14~", MCKEY_NOACTION }, - { KEY_F (5), ESC_STR "[15~", MCKEY_NOACTION }, - { KEY_F (6), ESC_STR "[17~", MCKEY_NOACTION }, - { KEY_F (7), ESC_STR "[18~", MCKEY_NOACTION }, - { KEY_F (8), ESC_STR "[19~", MCKEY_NOACTION }, - { KEY_F (9), ESC_STR "[20~", MCKEY_NOACTION }, - { KEY_F (10), ESC_STR "[21~", MCKEY_NOACTION }, // old xterm Shift-arrows { KEY_M_SHIFT | KEY_UP, ESC_STR "O2A", MCKEY_NOACTION }, @@ -327,39 +331,9 @@ static key_define_t xterm_key_defines[] = { { KEY_M_SHIFT | KEY_RIGHT, ESC_STR "O2C", MCKEY_NOACTION }, { KEY_M_SHIFT | KEY_LEFT, ESC_STR "O2D", MCKEY_NOACTION }, - // new xterm Shift-arrows - { KEY_M_SHIFT | KEY_UP, ESC_STR "[1;2A", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_DOWN, ESC_STR "[1;2B", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_RIGHT, ESC_STR "[1;2C", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_LEFT, ESC_STR "[1;2D", MCKEY_NOACTION }, - // more xterm keys with modifiers - { KEY_M_CTRL | KEY_PPAGE, ESC_STR "[5;5~", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_NPAGE, ESC_STR "[6;5~", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_IC, ESC_STR "[2;5~", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_DC, ESC_STR "[3;5~", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_HOME, ESC_STR "[1;5H", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_END, ESC_STR "[1;5F", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_HOME, ESC_STR "[1;2H", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_END, ESC_STR "[1;2F", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_UP, ESC_STR "[1;5A", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_DOWN, ESC_STR "[1;5B", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_RIGHT, ESC_STR "[1;5C", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_LEFT, ESC_STR "[1;5D", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_IC, ESC_STR "[2;2~", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_DC, ESC_STR "[3;2~", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_M_CTRL | KEY_UP, ESC_STR "[1;6A", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_M_CTRL | KEY_DOWN, ESC_STR "[1;6B", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_M_CTRL | KEY_RIGHT, ESC_STR "[1;6C", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_M_CTRL | KEY_LEFT, ESC_STR "[1;6D", MCKEY_NOACTION }, { KEY_M_SHIFT | '\t', ESC_STR "[Z", MCKEY_NOACTION }, - // putty - { KEY_M_SHIFT | KEY_M_CTRL | KEY_UP, ESC_STR "[[1;6A", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_M_CTRL | KEY_DOWN, ESC_STR "[[1;6B", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_M_CTRL | KEY_RIGHT, ESC_STR "[[1;6C", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_M_CTRL | KEY_LEFT, ESC_STR "[[1;6D", MCKEY_NOACTION }, - // putty alt-arrow keys // removed as source esc esc esc trouble /* @@ -371,39 +345,7 @@ static key_define_t xterm_key_defines[] = { { KEY_M_ALT | KEY_NPAGE, ESC_STR ESC_STR "[6~", MCKEY_NOACTION }, { KEY_M_ALT | KEY_HOME, ESC_STR ESC_STR "[1~", MCKEY_NOACTION }, { KEY_M_ALT | KEY_END, ESC_STR ESC_STR "[4~", MCKEY_NOACTION }, - - { KEY_M_CTRL | KEY_M_ALT | KEY_UP, ESC_STR ESC_STR "[1;2A", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_DOWN, ESC_STR ESC_STR "[1;2B", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_RIGHT, ESC_STR ESC_STR "[1;2C", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_LEFT, ESC_STR ESC_STR "[1;2D", MCKEY_NOACTION }, - - { KEY_M_CTRL | KEY_M_ALT | KEY_PPAGE, ESC_STR ESC_STR "[[5;5~", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_NPAGE, ESC_STR ESC_STR "[[6;5~", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_HOME, ESC_STR ESC_STR "[1;5H", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_END, ESC_STR ESC_STR "[1;5F", MCKEY_NOACTION }, */ - // xterm alt-arrow keys - { KEY_M_ALT | KEY_UP, ESC_STR "[1;3A", MCKEY_NOACTION }, - { KEY_M_ALT | KEY_DOWN, ESC_STR "[1;3B", MCKEY_NOACTION }, - { KEY_M_ALT | KEY_RIGHT, ESC_STR "[1;3C", MCKEY_NOACTION }, - { KEY_M_ALT | KEY_LEFT, ESC_STR "[1;3D", MCKEY_NOACTION }, - { KEY_M_ALT | KEY_PPAGE, ESC_STR "[5;3~", MCKEY_NOACTION }, - { KEY_M_ALT | KEY_NPAGE, ESC_STR "[6;3~", MCKEY_NOACTION }, - { KEY_M_ALT | KEY_HOME, ESC_STR "[1~", MCKEY_NOACTION }, - { KEY_M_ALT | KEY_END, ESC_STR "[4~", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_UP, ESC_STR "[1;7A", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_DOWN, ESC_STR "[1;7B", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_RIGHT, ESC_STR "[1;7C", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_LEFT, ESC_STR "[1;7D", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_PPAGE, ESC_STR "[5;7~", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_NPAGE, ESC_STR "[6;7~", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_HOME, ESC_STR "OH", MCKEY_NOACTION }, - { KEY_M_CTRL | KEY_M_ALT | KEY_END, ESC_STR "OF", MCKEY_NOACTION }, - - { KEY_M_SHIFT | KEY_M_ALT | KEY_UP, ESC_STR "[1;4A", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_M_ALT | KEY_DOWN, ESC_STR "[1;4B", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_M_ALT | KEY_RIGHT, ESC_STR "[1;4C", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_M_ALT | KEY_LEFT, ESC_STR "[1;4D", MCKEY_NOACTION }, // rxvt keys with modifiers { KEY_M_SHIFT | KEY_UP, ESC_STR "[a", MCKEY_NOACTION }, @@ -452,14 +394,6 @@ static key_define_t xterm_key_defines[] = { { KEY_M_SHIFT | KEY_M_CTRL | KEY_RIGHT, ESC_STR "O6C", MCKEY_NOACTION }, { KEY_M_SHIFT | KEY_M_CTRL | KEY_LEFT, ESC_STR "O6D", MCKEY_NOACTION }, - // iTerm - { KEY_M_SHIFT | KEY_PPAGE, ESC_STR "[5;2~", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_NPAGE, ESC_STR "[6;2~", MCKEY_NOACTION }, - - // putty - { KEY_M_SHIFT | KEY_PPAGE, ESC_STR "[[5;53~", MCKEY_NOACTION }, - { KEY_M_SHIFT | KEY_NPAGE, ESC_STR "[[6;53~", MCKEY_NOACTION }, - // keypad keys { KEY_IC, ESC_STR "Op", MCKEY_NOACTION }, { KEY_DC, ESC_STR "On", MCKEY_NOACTION }, @@ -542,6 +476,113 @@ static key_define_t qansi_key_defines[] = { { 0, NULL, MCKEY_NOACTION }, }; +static key_kitty_t kitty_key_defines[] = { + { ESC_CHAR, 27, 'u' }, + { (int) '\r', 13, 'u' }, + { KEY_IC, 2, '~' }, + { KEY_DC, 3, '~' }, + { KEY_UP, 1, 'A' }, + { KEY_DOWN, 1, 'B' }, + { KEY_RIGHT, 1, 'C' }, + { KEY_LEFT, 1, 'D' }, + { KEY_PPAGE, 5, '~' }, + { KEY_NPAGE, 6, '~' }, + { KEY_HOME, 1, 'H' }, + { KEY_HOME, 7, '~' }, + { KEY_END, 1, 'F' }, + { KEY_END, 8, '~' }, + { -1, 57361, 'u' }, // PRINT_SCREEN + { -1, 57362, 'u' }, // PAUSE + { -1, 57363, 'u' }, // MENU + { KEY_F (1), 11, '~' }, + { KEY_F (1), 1, 'P' }, + { KEY_F (2), 12, '~' }, + { KEY_F (2), 1, 'Q' }, + { KEY_F (3), 13, '~' }, + { KEY_F (4), 14, '~' }, + { KEY_F (4), 1, 'S' }, + { KEY_F (5), 15, '~' }, + { KEY_F (6), 17, '~' }, + { KEY_F (7), 18, '~' }, + { KEY_F (8), 19, '~' }, + { KEY_F (9), 20, '~' }, + { KEY_F (10), 21, '~' }, + { KEY_F (11), 23, '~' }, + { KEY_F (12), 24, '~' }, + { KEY_F (13), 57376, 'u' }, + { KEY_F (14), 57377, '~' }, + { KEY_F (15), 57378, '~' }, + { KEY_F (16), 57379, '~' }, + { KEY_F (17), 57380, '~' }, + { KEY_F (18), 57381, '~' }, + { KEY_F (19), 57382, '~' }, + { KEY_F (20), 57383, '~' }, + { KEY_F (21), 57384, '~' }, + { KEY_F (22), 57385, '~' }, + { KEY_F (23), 57386, '~' }, + { KEY_F (24), 57387, '~' }, + { KEY_F (25), 57388, '~' }, + { KEY_F (26), 57389, '~' }, + { KEY_F (27), 57390, '~' }, + { KEY_F (28), 57391, '~' }, + { KEY_F (29), 57392, '~' }, + { KEY_F (30), 57393, '~' }, + { KEY_F (31), 57394, '~' }, + { KEY_F (32), 57395, '~' }, + { KEY_F (33), 57396, '~' }, + { KEY_F (34), 57397, '~' }, + { KEY_F (35), 57398, '~' }, + { (int) '0', 57399, 'u' }, // KP_0 + { (int) '1', 57400, 'u' }, // KP_1 + { (int) '2', 57401, 'u' }, // KP_2 + { (int) '3', 57402, 'u' }, // KP_3 + { (int) '4', 57403, 'u' }, // KP_4 + { (int) '5', 57404, 'u' }, // KP_5 + { (int) '6', 57405, 'u' }, // KP_6 + { (int) '7', 57406, 'u' }, // KP_7 + { (int) '8', 57407, 'u' }, // KP_8 + { (int) '9', 57408, 'u' }, // KP_9 + { (int) '.', 57409, 'u' }, // KP_DECIMAL + { (int) '/', 57410, 'u' }, // KP_DIVIDE + { KEY_KP_MULTIPLY, 57411, 'u' }, + { KEY_KP_SUBTRACT, 57412, 'u' }, + { KEY_KP_ADD, 57413, 'u' }, + { KEY_ENTER, 57414, 'u' }, // KP_ENTER + { -1, 57415, 'u' }, // KP_EQUAL + { -1, 57416, 'u' }, // KP_SEPARATOR + { KEY_LEFT, 57417, 'u' }, // KP_LEFT + { KEY_RIGHT, 57418, 'u' }, // KP_RIGHT + { KEY_UP, 57419, 'u' }, // KP_UP + { KEY_DOWN, 57420, 'u' }, // KP_DOWN + { KEY_PPAGE, 57421, 'u' }, // KP_PAGE_UP + { KEY_NPAGE, 57422, 'u' }, // KP_PAGE_DOWN + { KEY_HOME, 57423, 'u' }, // KP_HOME + { KEY_END, 57424, 'u' }, // KP_END + { KEY_IC, 57425, 'u' }, // KP_INSERT + { KEY_DC, 57426, 'u' }, // KP_DELETE + { -1, 1, 'E' }, // KP_BEGIN + { -1, 57427, '~' }, // KP_BEGIN + + // MEDIA_* keys omitted + + { -1, 57441, 'u' }, // LEFT_SHIFT + { -1, 57442, 'u' }, // LEFT_CONTROL + { -1, 57443, 'u' }, // LEFT_ALT + { -1, 57444, 'u' }, // LEFT_SUPER + { -1, 57445, 'u' }, // LEFT_HYPER + { -1, 57446, 'u' }, // LEFT_META + { -1, 57447, 'u' }, // RIGHT_SHIFT + { -1, 57448, 'u' }, // RIGHT_CONTROL + { -1, 57449, 'u' }, // RIGHT_ALT + { -1, 57450, 'u' }, // RIGHT_SUPER + { -1, 57451, 'u' }, // RIGHT_HYPER + { -1, 57452, 'u' }, // RIGHT_META + { -1, 57453, 'u' }, // ISO_LEVEL3_SHIFT + { -1, 57454, 'u' }, // ISO_LEVEL5_SHIFT + + { 0, 0, 0 }, +}; + /* This holds all the key definitions */ static key_def *keys = NULL; @@ -1301,6 +1342,116 @@ lookup_keycode (const int code, int *idx) return FALSE; } +/* --------------------------------------------------------------------------------------------- */ +/** + * Check for Kitty Keyboard Protocol including backward compatibility sequences: + * + * CSI parameters [u~ABCDEFHPQS] + * + * https://sw.kovidgoyal.net/kitty/keyboard-protocol/ + * + * Examples: + * + * \E[2~ or \E[2;1~ -> KEY_IC + * \E[1;2A -> KEY_M_SHIFT | KEY_UP + * \E[13;6u -> KEY_M_CTRL | KEY_M_SHIFT | '\n' + * \E[111;5u\E[57421;5:2u -> XCTRL(KEY_M_CTRL | 'o'), KEY_M_CTRL | KEY_PPAGE + * \E[44:63;132u -> KEY_M_ALT | '?' (on Czech keyboard, with Num Lock on) + */ +static int +parse_kitty (csi_command_t csi) +{ + int c; + guint unicode_key_code, shifted_key, base_layout_key, modifiers; + gchar utf8char[MB_LEN_MAX + 1]; + + if (!((csi.final_byte == 'u' && csi.param_count >= 1) + || (csi.final_byte == '~' && csi.param_count >= 1) + || (csi.final_byte >= 'A' && csi.final_byte <= 'F') || (csi.final_byte == 'H') + || (csi.final_byte >= 'P' && csi.final_byte <= 'S'))) + return -1; + + // Private mode is used for reporting protocol presence and mode from terminal + // (CSI ? flags u). Currently we don't need this info so we don't process it + if (csi.private_mode != 0) + return -1; + + unicode_key_code = csi.params[0][0]; + shifted_key = csi.params[0][1]; + base_layout_key = csi.params[0][2]; + modifiers = csi.params[1][0]; + + if (modifiers != 0) + modifiers = ((modifiers - 1) << 12) & KEY_M_MASK; + + // Zero parameters are allowed. In that case, assume key code 1 as the default + if (csi.param_count == 0) + unicode_key_code = 1; + + // Check for non-literal keys using a predefined table + for (int j = 0; kitty_key_defines[j].code != 0; j++) + { + if (kitty_key_defines[j].kitty_code == unicode_key_code + && kitty_key_defines[j].final_byte == csi.final_byte) + { + c = kitty_key_defines[j].code; + if (c == -1) + return -1; + c |= modifiers; + + return c; + } + } + + // All possible keys (except for a few backward compatibility exceptions) are + // defined in the Unicode Basic Multilingual Plane Private Use Area. Discard + // unknown ones + if (unicode_key_code >= 0xE000 && unicode_key_code <= 0xF8FF) + return -1; + + // Translate shifted chars so command line input works + if (shifted_key) + { + c = shifted_key; + modifiers &= ~KEY_M_SHIFT; + } + else + { + c = unicode_key_code; + if ((base_layout_key || (c >= '0' && c <= '9'))) + modifiers &= ~KEY_M_SHIFT; + } + + // Check if it is a non-Basic Latin Unicode character. If so, convert it to UTF-8 + // and return the remaining bytes into the input queue. + if (c >= 0x80) + { + // If it's a CTRL or ALT combination and we have a base layout key, use it instead. + if (base_layout_key && (modifiers & (KEY_M_CTRL | KEY_M_ALT)) != 0) + c = base_layout_key; + // Otherwise, use the Unicode character and ignore modifiers. + else + { + const int utflen = g_unichar_to_utf8 (c, (gchar *) &utf8char); + + c = utf8char[0] & 0xFF; + for (int j = 1; j < utflen; j++) + push_char (utf8char[j] & 0xFF); + + pending_keys = seq_buffer; + return c; + } + } + + // Mask the resulting value with 0x1F if the CTRL modifier is set + if ((modifiers & KEY_M_CTRL) && (c == ' ' || (c >= 0x40 && c <= 0x7e))) + c = XCTRL (c); + + c |= modifiers; + + return c; +} + /* --------------------------------------------------------------------------------------------- */ /*** public functions ****************************************************************************/ /* --------------------------------------------------------------------------------------------- */ @@ -1714,10 +1865,9 @@ get_key_code (int no_delay) pend_send: if (pending_keys != NULL) { - /* At this point, no sequence was found in the keys tree. * - * Now discard all CSI and SS3 sequences, as these two are the de-facto standard for + * Now check for all CSI and SS3 sequences, as these two are the de-facto standard for * sending key sequences in most terminals. The only well-known deviations are: * - Linux console (it sends invalid CSI for F1-F5, as double '[' is not allowed in CSI); * this is handled by defining them in our key defines table (see above). @@ -1726,6 +1876,10 @@ get_key_code (int no_delay) * As a workaround, we treat SS3 like CSI. * - Some terminals begin sequences with ESC ESC to indicate Alt modifier (for example * PuTTY). + * + * Parse the sequence, check if it matches any of the complex sequences we are interested + * in, and discard it completely if it is unknown. Leave any keys following these sequences + * in stdin. */ if (pending_keys[0] == ESC_CHAR && pending_keys[1] == ESC_CHAR) @@ -1733,14 +1887,40 @@ get_key_code (int no_delay) if (pending_keys[0] == ESC_CHAR && (pending_keys[1] == '[' || pending_keys[1] == 'O')) { + int seqlen; + c = seq_append[-1]; // Get all parameter bytes and intermediate bytes before the final byte while (c >= ' ' && c <= '?') + { c = tty_lowlevel_getch (); + push_char (c); + } + + char seq_char_buffer[SEQ_BUFFER_LEN]; // Must have same length as seq_buffer + + for (seqlen = 0; pending_keys[seqlen] != '\0'; seqlen++) + seq_char_buffer[seqlen] = pending_keys[seqlen]; + seq_char_buffer[seqlen] = '\0'; pending_keys = seq_append = NULL; + if (seq_char_buffer[1] == '[') // CSI + { + csi_command_t csi; + + const char *seq_char_buffer_ptr = &seq_char_buffer[2]; + if (!parse_csi (&csi, &seq_char_buffer_ptr, &seq_char_buffer[seqlen])) + return -1; + + // Check for Kitty Keyboard Protocol sequence (or any common backwards compatible + // sequence) + c = parse_kitty (csi); + if (c != -1) + goto done; + } + return -1; } diff --git a/lib/tty/key.h b/lib/tty/key.h index 87691cec8..fce70cd21 100644 --- a/lib/tty/key.h +++ b/lib/tty/key.h @@ -5,8 +5,9 @@ #ifndef MC__KEY_H #define MC__KEY_H -#include "lib/global.h" // -#include "tty.h" // KEY_F macro +#include "lib/global.h" // +#include "tty.h" // KEY_F macro +#include "lib/terminal.h" // csi_command_t struct /*** typedefs(not structures) and defined constants **********************************************/ diff --git a/lib/tty/tty-ncurses.c b/lib/tty/tty-ncurses.c index ebdfc2a04..32add8c01 100644 --- a/lib/tty/tty-ncurses.c +++ b/lib/tty/tty-ncurses.c @@ -307,6 +307,7 @@ tty_init (gboolean mouse_enable, gboolean is_xterm) noecho (); keypad (stdscr, TRUE); nodelay (stdscr, FALSE); + tty_kitty (TRUE); tty_setup_sigwinch (sigwinch_handler); } @@ -317,6 +318,7 @@ void tty_shutdown (void) { tty_destroy_winch_pipe (); + tty_kitty (FALSE); tty_reset_shell_mode (); tty_noraw_mode (); tty_keypad (FALSE); diff --git a/lib/tty/tty-slang.c b/lib/tty/tty-slang.c index 632a88b97..f3e38c093 100644 --- a/lib/tty/tty-slang.c +++ b/lib/tty/tty-slang.c @@ -329,6 +329,7 @@ tty_init (gboolean mouse_enable, gboolean is_xterm) tty_enter_ca_mode (); tty_keypad (TRUE); tty_nodelay (FALSE); + tty_kitty (TRUE); tty_setup_sigwinch (sigwinch_handler); } @@ -341,6 +342,7 @@ tty_shutdown (void) char *op_cap; tty_destroy_winch_pipe (); + tty_kitty (FALSE); tty_reset_shell_mode (); tty_noraw_mode (); tty_keypad (FALSE); diff --git a/lib/tty/tty.c b/lib/tty/tty.c index 5a9ed7f3a..4ff01cb75 100644 --- a/lib/tty/tty.c +++ b/lib/tty/tty.c @@ -422,3 +422,23 @@ tty_init_xterm_support (gboolean is_xterm) } /* --------------------------------------------------------------------------------------------- */ + +/** Enable or disable Kitty Keyboard Protocol */ +void +tty_kitty (gboolean set) +{ + if (!mc_global.tty.kitty_keyboard_protocol) + return; + + if (set) + { + // 1 = Disambiguate escape codes + // 4 = Report alternate keys (required for detecting Alt+Shift+key) + // 8 = Report all keys as escape codes (required for distinguishing keypad keys [+-*]) + tty_putp (ESC_STR "[>13u"); + } + else + tty_putp (ESC_STR "[