diff --git a/src/fish_key_reader.cpp b/src/fish_key_reader.cpp index ef587d3c3..5a33feeec 100644 --- a/src/fish_key_reader.cpp +++ b/src/fish_key_reader.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include @@ -23,15 +22,16 @@ #include "fallback.h" // IWYU pragma: keep #include "input.h" #include "input_common.h" +#include "proc.h" +#include "reader.h" struct config_paths_t determine_config_directory_paths(const char *argv0); -static struct termios saved_modes; // so we can reset the modes when we're done static long long int prev_tstamp = 0; static const char *ctrl_equivalents[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\a", - "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, "\\e", NULL, NULL, NULL, NULL}; + "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, "\\e", NULL, NULL, NULL, NULL}; /// Return true if the recent sequence of characters indicates the user wants to exit the program. bool should_exit(unsigned char c) { @@ -45,7 +45,7 @@ bool should_exit(unsigned char c) { } /// Return the key name if the recent sequence of characters matches a known terminfo sequence. -char * const key_name(unsigned char c) { +char *const key_name(unsigned char c) { static char recent_chars[8] = {0}; recent_chars[0] = recent_chars[1]; @@ -119,7 +119,7 @@ void process_input(bool continuous_mode) { printf("%6lld usec dec: %3u hex: %2x char: %c\n", delta_tstamp, c, c, c); } - char * const name = key_name(c); + char *const name = key_name(c); if (name) { printf("FYI: Saw sequence for bind key name \"%s\"\n", name); free(name); @@ -134,48 +134,22 @@ void process_input(bool continuous_mode) { } } -/// Set the tty modes to not interpret any characters. We want every character to be passed thru to -/// this program. Including characters such as [ctrl-C] and [ctrl-D] that might normally have -/// special significance (e.g., terminate the program). -bool set_tty_modes(void) { - struct termios modes; - - tcgetattr(0, &modes); // get the current tty modes - saved_modes = modes; // save a copy so we can reset them on exit - - modes.c_lflag &= ~ICANON; // turn off canonical mode - modes.c_lflag &= ~ECHO; // turn off echo mode - modes.c_lflag &= ~ISIG; // turn off recognizing signal generating characters - modes.c_iflag &= ~ICRNL; // turn off mapping CR to NL - modes.c_iflag &= ~INLCR; // turn off mapping NL to CR - modes.c_cc[VMIN] = 1; // return each character as they arrive - modes.c_cc[VTIME] = 0; // wait forever for the next character - - if (tcsetattr(0, TCSANOW, &modes) != 0) { // set the new modes - return false; - } - return true; -} - -/// Restore the tty modes to what they were before this program was run. This shouldn't be required -/// but we do it just in case the program that ran us doesn't handle tty modes for external programs -/// in a sensible manner. -void reset_tty_modes() { tcsetattr(0, TCSANOW, &saved_modes); } - /// Make sure we cleanup before exiting if we're signaled. void signal_handler(int signo) { printf("\nExiting on receipt of signal #%d\n", signo); - reset_tty_modes(); + restore_term_mode(); exit(1); } /// Setup our environment (e.g., tty modes), process key strokes, then reset the environment. void setup_and_process_keys(bool continuous_mode) { + is_interactive_session = 1; // by definition this is interactive set_main_thread(); setup_fork_guards(); wsetlocale(LC_ALL, L"POSIX"); program_name = L"fish_key_reader"; env_init(); + reader_init(); input_init(); // Installing our handler for every signal (e.g., SIGSEGV) is dubious because it means that @@ -196,13 +170,9 @@ void setup_and_process_keys(bool continuous_mode) { set_wait_on_escape_ms(500); } - if (!set_tty_modes()) { - printf("Could not set the tty modes. Refusing to continue running.\n"); - exit(1); - } // TODO: We really should enable keypad mode but see issue #838. process_input(continuous_mode); - reset_tty_modes(); + restore_term_mode(); } int main(int argc, char **argv) { diff --git a/src/reader.cpp b/src/reader.cpp index 18f2a7311..396eb6b9b 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -1026,31 +1026,25 @@ void reader_init() /* Set the mode used for the terminal, initialized to the current mode */ memcpy(&shell_modes, &terminal_mode_on_startup, sizeof shell_modes); - shell_modes.c_iflag &= ~ICRNL; /* turn off mapping CR (\cM) to NL (\cJ) */ - shell_modes.c_iflag &= ~INLCR; /* turn off mapping NL (\cJ) to CR (\cM) */ - shell_modes.c_lflag &= ~ICANON; /* turn off canonical mode */ - shell_modes.c_lflag &= ~ECHO; /* turn off echo mode */ - shell_modes.c_iflag &= ~IXON; /* disable flow control */ - shell_modes.c_iflag &= ~IXOFF; /* disable flow control */ - shell_modes.c_cc[VMIN]=1; - shell_modes.c_cc[VTIME]=0; -#if defined(_POSIX_VDISABLE) - // PCA disable VDSUSP (typically control-Y), which is a funny job control - // function available only on OS X and BSD systems - // This lets us use control-Y for yank instead -#ifdef VDSUSP - shell_modes.c_cc[VDSUSP] = _POSIX_VDISABLE; -#endif -#endif + shell_modes.c_iflag &= ~ICRNL; // disable mapping CR (\cM) to NL (\cJ) + shell_modes.c_iflag &= ~INLCR; // disable mapping NL (\cJ) to CR (\cM) + shell_modes.c_iflag &= ~IXON; // disable flow control + shell_modes.c_iflag &= ~IXOFF; // disable flow control - // We don't use term_steal because this can fail if fd 0 isn't associated - // with a tty and this function is run regardless of whether stdin is tied - // to a tty. This is harmless in that case. We do it unconditionally - // because disabling ICRNL mode (see above) needs to be done at the - // earliest possible moment. Doing it here means it will be done within - // approximately 1 ms of the start of the shell rather than 250 ms (or - // more) when reader_interactive_init is eventually called. + shell_modes.c_lflag &= ~ICANON; // turn off canonical mode + shell_modes.c_lflag &= ~ECHO; // turn off echo mode + shell_modes.c_lflag &= ~IEXTEN; // turn off handling of discard and lnext characters + + shell_modes.c_cc[VMIN] = 1; + shell_modes.c_cc[VTIME] = 0; + + // We don't use term_steal because this can fail if fd 0 isn't associated with a tty and this + // function is run regardless of whether stdin is tied to a tty. This is harmless in that case. + // We do it unconditionally because disabling ICRNL mode (see above) needs to be done at the + // earliest possible moment. Doing it here means it will be done within approximately 1 ms of + // the start of the shell rather than 250 ms (or more) when reader_interactive_init is + // eventually called. // // TODO: Remove this condition when issue #2315 and #1041 are addressed. if (is_interactive_session) diff --git a/tests/bind.expect b/tests/bind.expect index 7589fa8e8..7c5942410 100644 --- a/tests/bind.expect +++ b/tests/bind.expect @@ -199,3 +199,23 @@ expect_prompt -re {\r\nmno pqrt\r\n} { } unmatched { puts stderr "emacs transpose words fail, 100ms timeout: long delay" } + +# Verify special characters, such as \cV, are not intercepted by the kernel +# tty driver. Rather, they can be bound and handled by fish. +send "bind \\cV 'echo ctrl-v seen'\r" +expect_prompt +send "\026\r" +expect_prompt -re {ctrl-v seen} { + puts "ctrl-v seen" +} unmatched { + puts stderr "ctrl-v not seen" +} + +send "bind \\cO 'echo ctrl-o seen'\r" +expect_prompt +send "\017\r" +expect_prompt -re {ctrl-o seen} { + puts "ctrl-o seen" +} unmatched { + puts stderr "ctrl-o not seen" +} diff --git a/tests/bind.expect.out b/tests/bind.expect.out index f74d54b2f..07f09fdec 100644 --- a/tests/bind.expect.out +++ b/tests/bind.expect.out @@ -13,3 +13,5 @@ default-mode custom timeout set correctly emacs transpose words, 100ms timeout: no delay emacs transpose words, 100ms timeout: short delay emacs transpose words, 100ms timeout: long delay +ctrl-v seen +ctrl-o seen