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,
pass_fds: tuple[int, ...] = (),
remote_control_fd: int = -1,
hold_after_ssh: bool = False
):
self.is_clone_launch = is_clone_launch
self.id = next(child_counter)
@ -230,7 +231,7 @@ class Child:
self.remote_control_fd = remote_control_fd
if cwd_from:
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:
log_error(f'Failed to read cwd of {cwd_from} with error: {err}')
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
than the newest foreground process. Finally, the special value :code:`root`
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
@ -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::
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
hold: bool
bias: float | None
hold_after_ssh: bool
def apply_colors(window: Window, spec: Sequence[str]) -> None:
@ -636,6 +644,7 @@ def _launch(
'stdin': None,
'hold': False,
'bias': None,
'hold_after_ssh': False
}
spacing = {}
if opts.spacing:
@ -658,6 +667,11 @@ def _launch(
kw['cwd_from'] = CwdRequest(source_window, CwdRequestType.root)
else:
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':
kw['location'] = opts.location
if opts.copy_colors and source_window:
@ -804,7 +818,7 @@ def clone_safe_opts() -> frozenset[str]:
return frozenset((
'window_title', 'tab_title', 'type', 'keep_focus', 'cwd', 'env', 'var', 'hold',
'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
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
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'

View file

@ -480,6 +480,7 @@ class Tab: # {{{
hold: bool = False,
pass_fds: tuple[int, ...] = (),
remote_control_fd: int = -1,
hold_after_ssh: bool = False
) -> Child:
check_for_suitability = True
if cmd is None:
@ -529,7 +530,7 @@ class Tab: # {{{
fenv['WINDOWID'] = str(pwid)
ans = Child(
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()
return ans
@ -568,11 +569,12 @@ class Tab: # {{{
pass_fds: tuple[int, ...] = (),
remote_control_fd: int = -1,
next_to: Window | None = None,
hold_after_ssh: bool = False
) -> Window:
child = self.launch_child(
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,
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(
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_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
if not window:
return ''
@ -166,9 +166,13 @@ class CwdRequest:
run_shell = argv[0] == resolved_shell(get_options())[0]
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
argv[:] = ssh_kitten_cmdline
if argv and argv[0] == 'kitten':
argv[0] = kitten_exe()
if ssh_kitten_cmdline and ssh_kitten_cmdline[0] == 'kitten':
if hold_after_ssh:
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_server_args_in_cmdline(server_args, argv, allocate_tty=not run_shell)
if env is not None: