From 7215c6d6be0189026b7c64d4f9d8b1c992ed483d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 Jul 2022 19:11:14 +0530 Subject: [PATCH] ssh kitten: Allow pressing Ctrl-C to abort ssh before the connection is completed Fixing this involved adding a new mode to kitty where it handles ctrl-c/z/q by sending signals to the tty foreground process group instead of delegating to the kernel to do that. Since the pipe may be full we have no way of knowing when the kernel will get around to reading the signal byte. So send the signal ourselves. Fixes #5271 --- docs/changelog.rst | 3 +++ kittens/ssh/main.py | 8 +++++++- kittens/tui/operations.py | 1 + kitty/child.py | 21 +++++++++++++++++++++ kitty/keys.c | 3 +++ kitty/modes.h | 3 +++ kitty/screen.c | 15 +++++++++++++++ kitty/screen.h | 3 ++- kitty/window.py | 7 +++++++ 9 files changed, 62 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index dd6cf8fa7..8918396c0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -71,6 +71,9 @@ Detailed list of changes - ssh kitten: A new option :code:`--symlink-strategy` to control how symlinks are copied to the remote machine (:iss:`5249`) +- ssh kitten: Allow pressing Ctrl-C to abort ssh before the connection is + completed (:iss:`5271`) + - Bash integration: Fix declare not creating global variables in .bashrc (:iss:`5254`) diff --git a/kittens/ssh/main.py b/kittens/ssh/main.py index 976023af5..11f6b7d5c 100644 --- a/kittens/ssh/main.py +++ b/kittens/ssh/main.py @@ -39,7 +39,10 @@ from kitty.utils import ( set_echo as turn_off_echo ) -from ..tui.operations import restore_colors, save_colors +from ..tui.operations import ( + RESTORE_PRIVATE_MODE_VALUES, SAVE_PRIVATE_MODE_VALUES, Mode, + restore_colors, save_colors, set_mode +) from ..tui.utils import kitty_opts, running_in_tmux from .config import init_config from .copy import CopyInstruction @@ -547,10 +550,13 @@ def connection_sharing_args(kitty_pid: int) -> List[str]: def restore_terminal_state() -> Iterator[bool]: with open(os.ctermid()) as f: val = termios.tcgetattr(f.fileno()) + print(end=SAVE_PRIVATE_MODE_VALUES) + print(end=set_mode(Mode.HANDLE_TERMIOS_SIGNALS), flush=True) try: yield bool(val[3] & termios.ECHO) finally: termios.tcsetattr(f.fileno(), termios.TCSAFLUSH, val) + print(end=RESTORE_PRIVATE_MODE_VALUES, flush=True) def dcs_to_kitty(payload: Union[bytes, str], type: str = 'ssh') -> bytes: diff --git a/kittens/tui/operations.py b/kittens/tui/operations.py index 9b613dbac..f2f146df0 100644 --- a/kittens/tui/operations.py +++ b/kittens/tui/operations.py @@ -47,6 +47,7 @@ class Mode(Enum): ALTERNATE_SCREEN = 1049, '?' BRACKETED_PASTE = 2004, '?' PENDING_UPDATE = 2026, '?' + HANDLE_TERMIOS_SIGNALS = 19997, '?' def cmd(f: F) -> F: diff --git a/kitty/child.py b/kitty/child.py index 4911dd9e1..b377444da 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -448,3 +448,24 @@ class Child: with suppress(Exception): return environ_of_process(pid) return {} + + def send_signal_for_key(self, key_num: int) -> bool: + import signal + import termios + if self.child_fd is None: + return False + t = termios.tcgetattr(self.child_fd) + if not t[3] & termios.ISIG: + return False + cc = t[-1] + if key_num == cc[termios.VINTR]: + s = signal.SIGINT + elif key_num == cc[termios.VSUSP]: + s = signal.SIGTSTP + elif key_num == cc[termios.VQUIT]: + s = signal.SIGQUIT + else: + return False + pgrp = os.tcgetpgrp(self.child_fd) + os.killpg(pgrp, s) + return True diff --git a/kitty/keys.c b/kitty/keys.c index 965ee74f0..246076e03 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -218,6 +218,9 @@ on_key_input(GLFWkeyevent *ev) { schedule_write_to_child(w->id, 1, text, strlen(text)); debug("sent key as text to child\n"); } else if (size > 0) { + if (size == 1 && screen->modes.mHANDLE_TERMIOS_SIGNALS) { + if (screen_send_signal_for_key(screen, *encoded_key)) return; + } schedule_write_to_child(w->id, 1, encoded_key, size); debug("sent encoded key to child\n"); } else { diff --git a/kitty/modes.h b/kitty/modes.h index 5f0ef045d..aabbd9d38 100644 --- a/kitty/modes.h +++ b/kitty/modes.h @@ -84,3 +84,6 @@ // Pending updates mode #define PENDING_UPDATE (2026 << 5) + +// Handle Ctrl-C/Ctrl-Z mode +#define HANDLE_TERMIOS_SIGNALS (19997 << 5) diff --git a/kitty/screen.c b/kitty/screen.c index 296c22cde..85a544ac8 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -984,6 +984,7 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) { SIMPLE_MODE(DECARM) SIMPLE_MODE(BRACKETED_PASTE) SIMPLE_MODE(FOCUS_TRACKING) + SIMPLE_MODE(HANDLE_TERMIOS_SIGNALS) MOUSE_MODE(MOUSE_BUTTON_TRACKING, mouse_tracking_mode, BUTTON_MODE) MOUSE_MODE(MOUSE_MOTION_TRACKING, mouse_tracking_mode, MOTION_MODE) MOUSE_MODE(MOUSE_MOVE_TRACKING, mouse_tracking_mode, ANY_MODE) @@ -2085,6 +2086,20 @@ screen_handle_cmd(Screen *self, PyObject *cmd) { CALLBACK("handle_remote_cmd", "O", cmd); } +bool +screen_send_signal_for_key(Screen *self, char key) { + int ret = 0; + if (self->callbacks != Py_None) { + int cchar = key; + PyObject *callback_ret = PyObject_CallMethod(self->callbacks, "send_signal_for_key", "c", cchar); + if (callback_ret) { + ret = PyObject_IsTrue(callback_ret); + Py_DECREF(callback_ret); + } else { PyErr_Print(); } + } + return ret != 0; +} + void screen_push_colors(Screen *self, unsigned int idx) { if (colorprofile_push_colors(self->color_profile, idx)) self->color_profile->dirty = true; diff --git a/kitty/screen.h b/kitty/screen.h index 10ea23562..cc6c9d27e 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -14,7 +14,7 @@ typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } Scr typedef struct { bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, mDECCKM, - mBRACKETED_PASTE, mFOCUS_TRACKING, mDECSACE; + mBRACKETED_PASTE, mFOCUS_TRACKING, mDECSACE, mHANDLE_TERMIOS_SIGNALS; MouseTrackingMode mouse_tracking_mode; MouseTrackingProtocol mouse_tracking_protocol; bool eight_bit_controls; // S8C1T @@ -263,6 +263,7 @@ void screen_report_key_encoding_flags(Screen *self); bool screen_detect_url(Screen *screen, unsigned int x, unsigned int y); int screen_cursor_at_a_shell_prompt(const Screen *); bool screen_fake_move_cursor_to_position(Screen *, index_type x, index_type y); +bool screen_send_signal_for_key(Screen *, char key); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) diff --git a/kitty/window.py b/kitty/window.py index 997c9dca8..9f2b5991c 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -857,6 +857,13 @@ class Window: '--ssh-connection-data', json.dumps(conn_data) ) + def send_signal_for_key(self, key_num: int) -> bool: + try: + return self.child.send_signal_for_key(key_num) + except OSError as err: + log_error(f'Failed to send signal for key to child with err: {err}') + return False + def focus_changed(self, focused: bool) -> None: if self.destroyed: return