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
This commit is contained in:
Kovid Goyal 2022-07-20 19:11:14 +05:30
parent bd9e1f58fe
commit 7215c6d6be
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
9 changed files with 62 additions and 2 deletions

View File

@ -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`)

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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 {

View File

@ -84,3 +84,6 @@
// Pending updates mode
#define PENDING_UPDATE (2026 << 5)
// Handle Ctrl-C/Ctrl-Z mode
#define HANDLE_TERMIOS_SIGNALS (19997 << 5)

View File

@ -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;

View File

@ -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)

View File

@ -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