diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 5a69732241..533572427c 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -643,24 +643,17 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key) switch (initial) { case '?': // Kitty keyboard protocol query response. - if (input->waiting_for_kkp_response) { - input->waiting_for_kkp_response = false; - input->key_encoding = kKeyEncodingKitty; - tui_set_key_encoding(input->tui_data); - } - + input->key_encoding = kKeyEncodingKitty; break; } break; case 'c': switch (initial) { case '?': - // Primary Device Attributes response - if (input->waiting_for_kkp_response) { - input->waiting_for_kkp_response = false; - - // Enable the fallback key encoding (if any) - tui_set_key_encoding(input->tui_data); + // Primary Device Attributes (DA1) response + if (input->callbacks.primary_device_attr) { + input->callbacks.primary_device_attr(input->tui_data); + input->callbacks.primary_device_attr = NULL; } break; diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index e48982f9a4..4c5a64df9d 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -17,6 +17,10 @@ typedef enum { kKeyEncodingXterm, ///< Xterm's modifyOtherKeys encoding (XTMODKEYS) } KeyEncoding; +typedef struct { + void (*primary_device_attr)(TUIData *tui); +} TermInputCallbacks; + #define KEY_BUFFER_SIZE 0x1000 typedef struct { int in_fd; @@ -24,8 +28,7 @@ typedef struct { int8_t paste; bool ttimeout; - bool waiting_for_kkp_response; ///< True if we are expecting to receive a response to a query for - ///< Kitty keyboard protocol support + TermInputCallbacks callbacks; KeyEncoding key_encoding; ///< The key encoding used by the terminal emulator diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 16f3d308d8..7332a84166 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -18,6 +18,7 @@ #include "nvim/cursor_shape.h" #include "nvim/event/defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/signal.h" #include "nvim/event/stream.h" #include "nvim/globals.h" @@ -46,6 +47,10 @@ # include "nvim/os/os_win_console.h" #endif +// Maximum amount of time (in ms) to wait to receive a Device Attributes +// response before exiting. +#define EXIT_TIMEOUT_MS 1000 + #define OUTBUF_SIZE 0xffff #define TOO_MANY_EVENTS 1000000 @@ -100,6 +105,7 @@ struct TUIData { bool bce; bool mouse_enabled; bool mouse_move_enabled; + bool mouse_enabled_save; bool title_enabled; bool sync_output; bool busy, is_invisible, want_invisible; @@ -287,7 +293,8 @@ void tui_enable_extended_underline(TUIData *tui) static void tui_query_kitty_keyboard(TUIData *tui) FUNC_ATTR_NONNULL_ALL { - tui->input.waiting_for_kkp_response = true; + // Set the key encoding whenever the Device Attributes (DA1) response is received. + tui->input.callbacks.primary_device_attr = tui_set_key_encoding; out(tui, S_LEN("\x1b[?u\x1b[c")); } @@ -514,8 +521,8 @@ static void terminfo_start(TUIData *tui) xfree(term_program_version_env); } -/// Disable the alternate screen and prepare for the TUI to close. -static void terminfo_stop(TUIData *tui) +/// Disable various terminal modes and other features. +static void terminfo_disable(TUIData *tui) { // Disable theme update notifications. We do this first to avoid getting any // more notifications after we reset the cursor and any color palette changes. @@ -540,16 +547,6 @@ static void terminfo_stop(TUIData *tui) // May restore old title before exiting alternate screen. tui_set_title(tui, NULL_STRING); - if (ui_client_exit_status == 0) { - ui_client_exit_status = tui->seen_error_exit; - } - // if nvim exited with nonzero status, without indicated this was an - // intentional exit (like `:1cquit`), it likely was an internal failure. - // Don't clobber the stderr error message in this case. - if (ui_client_exit_status == tui->seen_error_exit) { - // Exit alternate screen. - unibi_out(tui, unibi_exit_ca_mode); - } if (tui->cursor_has_color) { unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } @@ -558,6 +555,29 @@ static void terminfo_stop(TUIData *tui) // Disable focus reporting unibi_out_ext(tui, tui->unibi_ext.disable_focus_reporting); + // Send a DA1 request. When the terminal responds we know that it has + // processed all of our requests and won't be emitting anymore sequences. + out(tui, S_LEN("\x1b[c")); + + // Immediately flush the buffer and wait for the DA1 response. + flush_buf(tui); +} + +/// Disable the alternate screen and prepare for the TUI to close. +static void terminfo_stop(TUIData *tui) +{ + if (ui_client_exit_status == 0) { + ui_client_exit_status = tui->seen_error_exit; + } + + // if nvim exited with nonzero status, without indicated this was an + // intentional exit (like `:1cquit`), it likely was an internal failure. + // Don't clobber the stderr error message in this case. + if (ui_client_exit_status == tui->seen_error_exit) { + // Exit alternate screen. + unibi_out(tui, unibi_exit_ca_mode); + } + flush_buf(tui); uv_tty_reset_mode(); uv_close((uv_handle_t *)&tui->output_handle, NULL); @@ -592,8 +612,14 @@ static void tui_terminal_after_startup(TUIData *tui) flush_buf(tui); } -/// Stop the terminal but allow it to restart later (like after suspend) -static void tui_terminal_stop(TUIData *tui) +void tui_error_exit(TUIData *tui, Integer status) + FUNC_ATTR_NONNULL_ALL +{ + tui->seen_error_exit = (int)status; +} + +void tui_stop(TUIData *tui) + FUNC_ATTR_NONNULL_ALL { if (uv_is_closing((uv_handle_t *)&tui->output_handle)) { // Race between SIGCONT (tui.c) and SIGHUP (os/signal.c)? #8075 @@ -601,28 +627,42 @@ static void tui_terminal_stop(TUIData *tui) tui->stopped = true; return; } + + tui->input.callbacks.primary_device_attr = tui_stop_cb; + terminfo_disable(tui); + + // Wait until DA1 response is received + LOOP_PROCESS_EVENTS_UNTIL(tui->loop, tui->loop->events, EXIT_TIMEOUT_MS, tui->stopped); + + tui_terminal_stop(tui); + stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598) + tinput_destroy(&tui->input); + signal_watcher_stop(&tui->winch_handle); + signal_watcher_close(&tui->winch_handle, NULL); + uv_close((uv_handle_t *)&tui->startup_delay_timer, NULL); +} + +/// Callback function called when the response to the Device Attributes (DA1) +/// request is sent during shutdown. +static void tui_stop_cb(TUIData *tui) + FUNC_ATTR_NONNULL_ALL +{ + tui->stopped = true; +} + +/// Stop the terminal but allow it to restart later (like after suspend) +/// +/// This is called after we receive the response to the DA1 request sent from +/// terminfo_disable. +static void tui_terminal_stop(TUIData *tui) + FUNC_ATTR_NONNULL_ALL +{ tinput_stop(&tui->input); // Position the cursor on the last screen line, below all the text cursor_goto(tui, tui->height - 1, 0); terminfo_stop(tui); } -void tui_error_exit(TUIData *tui, Integer status) -{ - tui->seen_error_exit = (int)status; -} - -void tui_stop(TUIData *tui) -{ - tui_terminal_stop(tui); - stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598) - tinput_destroy(&tui->input); - tui->stopped = true; - signal_watcher_stop(&tui->winch_handle); - signal_watcher_close(&tui->winch_handle, NULL); - uv_close((uv_handle_t *)&tui->startup_delay_timer, NULL); -} - /// Returns true if UI `ui` is stopped. bool tui_is_stopped(TUIData *tui) { @@ -1581,7 +1621,16 @@ void tui_suspend(TUIData *tui) // on a non-UNIX system, this is a no-op #ifdef UNIX ui_client_detach(); - bool enable_mouse = tui->mouse_enabled; + tui->mouse_enabled_save = tui->mouse_enabled; + tui->input.callbacks.primary_device_attr = tui_suspend_cb; + terminfo_disable(tui); +#endif +} + +#ifdef UNIX +static void tui_suspend_cb(TUIData *tui) + FUNC_ATTR_NONNULL_ALL +{ tui_terminal_stop(tui); stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598) @@ -1590,13 +1639,13 @@ void tui_suspend(TUIData *tui) tui_terminal_start(tui); tui_terminal_after_startup(tui); - if (enable_mouse) { + if (tui->mouse_enabled_save) { tui_mouse_on(tui); } stream_set_blocking(tui->input.in_fd, false); // libuv expects this ui_client_attach(tui->width, tui->height, tui->term, tui->rgb); -#endif } +#endif void tui_set_title(TUIData *tui, String title) { @@ -2481,7 +2530,9 @@ static void augment_terminfo(TUIData *tui, const char *term, int vte_version, in if (!kitty && (vte_version == 0 || vte_version >= 5400)) { // Fallback to Xterm's modifyOtherKeys if terminal does not support the - // Kitty keyboard protocol + // Kitty keyboard protocol. We don't actually enable the key encoding here + // though: it won't be enabled until the terminal responds to our query for + // kitty keyboard support. tui->input.key_encoding = kKeyEncodingXterm; }