feat: new option --hold-after-ssh to allow spawning a shell after a kitten ssh disconnection

new option to the launch command, the new shell will be in the directory
that launched kitten ssh
This commit is contained in:
Barr
2025-07-11 18:34:21 +03:00
parent 564195f94f
commit ca2232e435
5 changed files with 30 additions and 8 deletions

View File

@@ -221,6 +221,7 @@ class Child:
hold: bool = False, hold: bool = False,
pass_fds: tuple[int, ...] = (), pass_fds: tuple[int, ...] = (),
remote_control_fd: int = -1, remote_control_fd: int = -1,
hold_after_ssh: bool = False
): ):
self.is_clone_launch = is_clone_launch self.is_clone_launch = is_clone_launch
self.id = next(child_counter) self.id = next(child_counter)
@@ -230,7 +231,7 @@ class Child:
self.remote_control_fd = remote_control_fd self.remote_control_fd = remote_control_fd
if cwd_from: if cwd_from:
try: try:
cwd = cwd_from.modify_argv_for_launch_with_cwd(self.argv, env) or cwd cwd = cwd_from.modify_argv_for_launch_with_cwd(self.argv, env, hold_after_ssh=hold_after_ssh) or cwd
except Exception as err: except Exception as err:
log_error(f'Failed to read cwd of {cwd_from} with error: {err}') log_error(f'Failed to read cwd of {cwd_from} with error: {err}')
else: else:

View File

@@ -140,6 +140,8 @@ by the shell (needs :ref:`shell_integration` to work). The special value
oldest foreground process associated with the currently active window rather oldest foreground process associated with the currently active window rather
than the newest foreground process. Finally, the special value :code:`root` than the newest foreground process. Finally, the special value :code:`root`
refers to the process that was originally started when the window was created. refers to the process that was originally started when the window was created.
When running :code:`kitten ssh`, using :option:`--hold-after-ssh` will cause a shell
in the working directory that started the :code:`kitten ssh` to be opened after disconnecting.
--env --env
@@ -393,6 +395,11 @@ and :option:`--config <kitty +kitten panel --config>`. Can be specified multiple
For example, to create a desktop panel at the bottom of the screen two lines high:: For example, to create a desktop panel at the bottom of the screen two lines high::
launch --type os-panel --os-panel lines=2 --os-panel edge=bottom sh -c "echo; echo; echo hello; sleep 5s" launch --type os-panel --os-panel lines=2 --os-panel edge=bottom sh -c "echo; echo; echo hello; sleep 5s"
--hold-after-ssh
type=bool-set
When using :option:`--cwd=current` from a kitten ssh session, after disconnecting from the session on the new window, a shell will spawn.
""" """
@@ -536,6 +543,7 @@ class LaunchKwds(TypedDict):
stdin: bytes | None stdin: bytes | None
hold: bool hold: bool
bias: float | None bias: float | None
hold_after_ssh: bool
def apply_colors(window: Window, spec: Sequence[str]) -> None: def apply_colors(window: Window, spec: Sequence[str]) -> None:
@@ -636,6 +644,7 @@ def _launch(
'stdin': None, 'stdin': None,
'hold': False, 'hold': False,
'bias': None, 'bias': None,
'hold_after_ssh': False
} }
spacing = {} spacing = {}
if opts.spacing: if opts.spacing:
@@ -658,6 +667,11 @@ def _launch(
kw['cwd_from'] = CwdRequest(source_window, CwdRequestType.root) kw['cwd_from'] = CwdRequest(source_window, CwdRequestType.root)
else: else:
kw['cwd'] = opts.cwd kw['cwd'] = opts.cwd
if opts.hold_after_ssh:
if opts.cwd != 'current':
raise ValueError("--hold_after_ssh can only be supplied if --cwd=current is also supplied")
kw['hold_after_ssh'] = True
if opts.location != 'default': if opts.location != 'default':
kw['location'] = opts.location kw['location'] = opts.location
if opts.copy_colors and source_window: if opts.copy_colors and source_window:
@@ -804,7 +818,7 @@ def clone_safe_opts() -> frozenset[str]:
return frozenset(( return frozenset((
'window_title', 'tab_title', 'type', 'keep_focus', 'cwd', 'env', 'var', 'hold', 'window_title', 'tab_title', 'type', 'keep_focus', 'cwd', 'env', 'var', 'hold',
'location', 'os_window_class', 'os_window_name', 'os_window_title', 'os_window_state', 'location', 'os_window_class', 'os_window_name', 'os_window_title', 'os_window_state',
'logo', 'logo_position', 'logo_alpha', 'color', 'spacing', 'next_to', 'logo', 'logo_position', 'logo_alpha', 'color', 'spacing', 'next_to', 'hold_after_ssh'
)) ))

View File

@@ -57,6 +57,7 @@ class Launch(RemoteCommand):
watcher/list.str: list of paths to watcher files watcher/list.str: list of paths to watcher files
bias/float: The bias with which to create the new window in the current layout bias/float: The bias with which to create the new window in the current layout
wait_for_child_to_exit/bool: Boolean indicating whether to wait and return child exit code wait_for_child_to_exit/bool: Boolean indicating whether to wait and return child exit code
hold_after_ssh/bool: Boolean indicating whether to spawn a shell after exiting a kitten ssh opened by this launch, requires --cwd=current to also be supplied
''' '''
short_desc = 'Run an arbitrary process in a new window/tab' short_desc = 'Run an arbitrary process in a new window/tab'

View File

@@ -480,6 +480,7 @@ class Tab: # {{{
hold: bool = False, hold: bool = False,
pass_fds: tuple[int, ...] = (), pass_fds: tuple[int, ...] = (),
remote_control_fd: int = -1, remote_control_fd: int = -1,
hold_after_ssh: bool = False
) -> Child: ) -> Child:
check_for_suitability = True check_for_suitability = True
if cmd is None: if cmd is None:
@@ -529,7 +530,7 @@ class Tab: # {{{
fenv['WINDOWID'] = str(pwid) fenv['WINDOWID'] = str(pwid)
ans = Child( ans = Child(
cmd, cwd or self.cwd, stdin, fenv, cwd_from, is_clone_launch=is_clone_launch, cmd, cwd or self.cwd, stdin, fenv, cwd_from, is_clone_launch=is_clone_launch,
add_listen_on_env_var=add_listen_on_env_var, hold=hold, pass_fds=pass_fds, remote_control_fd=remote_control_fd) add_listen_on_env_var=add_listen_on_env_var, hold=hold, pass_fds=pass_fds, remote_control_fd=remote_control_fd, hold_after_ssh=hold_after_ssh)
ans.fork() ans.fork()
return ans return ans
@@ -568,11 +569,12 @@ class Tab: # {{{
pass_fds: tuple[int, ...] = (), pass_fds: tuple[int, ...] = (),
remote_control_fd: int = -1, remote_control_fd: int = -1,
next_to: Window | None = None, next_to: Window | None = None,
hold_after_ssh: bool = False
) -> Window: ) -> Window:
child = self.launch_child( child = self.launch_child(
use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env, use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env,
is_clone_launch=is_clone_launch, add_listen_on_env_var=False if allow_remote_control and remote_control_passwords else True, is_clone_launch=is_clone_launch, add_listen_on_env_var=False if allow_remote_control and remote_control_passwords else True,
hold=hold, pass_fds=pass_fds, remote_control_fd=remote_control_fd, hold=hold, pass_fds=pass_fds, remote_control_fd=remote_control_fd, hold_after_ssh=hold_after_ssh
) )
window = Window( window = Window(
self, child, self.args, override_title=override_title, self, child, self.args, override_title=override_title,

View File

@@ -155,7 +155,7 @@ class CwdRequest:
return window.get_cwd_of_root_child() or '' return window.get_cwd_of_root_child() or ''
return window.get_cwd_of_child(oldest=self.request_type is CwdRequestType.oldest) or '' return window.get_cwd_of_child(oldest=self.request_type is CwdRequestType.oldest) or ''
def modify_argv_for_launch_with_cwd(self, argv: list[str], env: dict[str, str] | None=None) -> str: def modify_argv_for_launch_with_cwd(self, argv: list[str], env: dict[str, str] | None=None, hold_after_ssh: bool = False) -> str:
window = self.window window = self.window
if not window: if not window:
return '' return ''
@@ -166,9 +166,13 @@ class CwdRequest:
run_shell = argv[0] == resolved_shell(get_options())[0] run_shell = argv[0] == resolved_shell(get_options())[0]
server_args = [] if run_shell else list(argv) server_args = [] if run_shell else list(argv)
from kittens.ssh.utils import set_cwd_in_cmdline, set_env_in_cmdline, set_server_args_in_cmdline from kittens.ssh.utils import set_cwd_in_cmdline, set_env_in_cmdline, set_server_args_in_cmdline
argv[:] = ssh_kitten_cmdline if ssh_kitten_cmdline and ssh_kitten_cmdline[0] == 'kitten':
if argv and argv[0] == 'kitten': if hold_after_ssh:
argv[0] = kitten_exe() argv[:] = [kitten_exe(), "run-shell", kitten_exe()] +ssh_kitten_cmdline[1:]
else:
argv[:] = [kitten_exe()]+ssh_kitten_cmdline[1:]
else:
argv[:] = ssh_kitten_cmdline
set_cwd_in_cmdline(reported_cwd, argv) set_cwd_in_cmdline(reported_cwd, argv)
set_server_args_in_cmdline(server_args, argv, allocate_tty=not run_shell) set_server_args_in_cmdline(server_args, argv, allocate_tty=not run_shell)
if env is not None: if env is not None: