From 8451ad44b595581f90f12129cf5dd3909c10b2fb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Aug 2025 10:37:05 +0530 Subject: [PATCH] Implement running foreground process even on unknown shells This is not as nice because the environment wont have been setup by the shell before running the process, but it's the best we can do for an unknown shell. --- kitty/child.py | 24 ++++++++++++++++++------ kitty/launch.py | 20 +++----------------- kitty/tabs.py | 13 +++++++++---- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/kitty/child.py b/kitty/child.py index 2f5a8f6ac..1e10b1f8b 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -13,7 +13,7 @@ import kitty.fast_data_types as fast_data_types from .constants import handled_signals, is_freebsd, is_macos, kitten_exe, kitty_base_dir, shell_path, terminfo_dir from .types import run_once -from .utils import cmdline_for_hold, log_error, which +from .utils import cmdline_for_hold, log_error, resolved_shell, which if TYPE_CHECKING: from .window import CwdRequest @@ -229,7 +229,8 @@ class Child: hold: bool = False, pass_fds: tuple[int, ...] = (), remote_control_fd: int = -1, - hold_after_ssh: bool = False + hold_after_ssh: bool = False, + startup_command_via_shell_integration: Sequence[str] = (), ): self.is_clone_launch = is_clone_launch self.id = next(child_counter) @@ -247,12 +248,13 @@ class Child: self.cwd = os.path.abspath(cwd) self.stdin = stdin self.env = env or {} + self.startup_command_via_shell_integration = startup_command_via_shell_integration self.final_env:dict[str, str] = {} self.is_default_shell = bool(self.argv and self.argv[0] == shell_path) self.should_run_via_run_shell_kitten = is_macos and self.is_default_shell self.hold = hold - def get_final_env(self) -> dict[str, str]: + def get_final_env(self) -> tuple[dict[str, str], bool]: from kitty.options.utils import DELETE_ENV_VAR env = default_env().copy() opts = fast_data_types.get_options() @@ -294,7 +296,15 @@ class Child: self.is_clone_launch = '1' # free memory else: env.pop('KITTY_IS_CLONE_LAUNCH', None) - return env + must_run_startup_command_via_kitten = False + if self.startup_command_via_shell_integration: + from .shell_integration import join + scmd = self.argv or resolved_shell(fast_data_types.get_options()) + try: + env['KITTY_SI_RUN_COMMAND_AT_STARTUP'] = join(scmd[0], self.startup_command_via_shell_integration) + except Exception: + must_run_startup_command_via_kitten = True # unknown shell + return env, must_run_startup_command_via_kitten def fork(self) -> int | None: if self.forked: @@ -312,13 +322,13 @@ class Child: os.set_inheritable(stdin_read_fd, True) else: stdin_read_fd = stdin_write_fd = -1 - self.final_env = self.get_final_env() + self.final_env, must_run_startup_command_via_kitten = self.get_final_env() argv = list(self.argv) cwd = self.cwd pass_fds = self.pass_fds if self.remote_control_fd > -1: pass_fds += self.remote_control_fd, - if self.should_run_via_run_shell_kitten: + if self.should_run_via_run_shell_kitten or must_run_startup_command_via_kitten: # bash will only source ~/.bash_profile if it detects it is a login # shell (see the invocation section of the bash man page), which it # does if argv[0] is prefixed by a hyphen see @@ -338,6 +348,8 @@ class Child: if ksi == 'invalid': ksi = 'enabled' argv = [kitten_exe(), 'run-shell', '--shell', shlex.join(argv), '--shell-integration', ksi] + if must_run_startup_command_via_kitten: + argv.extend(self.startup_command_via_shell_integration) if is_macos and not pass_fds and not opts.forward_stdio: # In addition for getlogin() to work we need to run the shell # via the /usr/bin/login wrapper, sigh. diff --git a/kitty/launch.py b/kitty/launch.py index e925a3051..86daeb9b9 100644 --- a/kitty/launch.py +++ b/kitty/launch.py @@ -17,7 +17,7 @@ from .fast_data_types import add_timer, get_boss, get_options, get_os_window_tit from .options.utils import env as parse_env from .tabs import Tab, TabManager from .types import LayerShellConfig, OverlayType, run_once -from .utils import get_editor, log_error, resolve_custom_file, resolved_shell, which +from .utils import get_editor, log_error, resolve_custom_file, which from .window import CwdRequest, CwdRequestType, Watchers, Window @@ -776,19 +776,9 @@ def _launch( tab = tab_for_window(boss, opts, target_tab, next_to) watchers = load_watch_modules(opts.watcher) with Window.set_ignore_focus_changes_for_new_windows(opts.keep_focus): - startup_command_env_added = False - if startup_command_via_shell_integration: - from .shell_integration import join - try: - scmd = kw.get('cmd') or resolved_shell(get_options()) - env = env or {} - env['KITTY_SI_RUN_COMMAND_AT_STARTUP'] = join(scmd[0], startup_command_via_shell_integration) - startup_command_env_added = True - except Exception: - pass # shell is not a known shell - new_window: Window = tab.new_window( - env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, next_to=next_to, **kw) + env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, next_to=next_to, + startup_command_via_shell_integration=startup_command_via_shell_integration, **kw) if child_death_callback is not None: boss.monitor_pid(new_window.child.pid or 0, child_death_callback) if new_window.creation_spec: @@ -798,10 +788,6 @@ def _launch( new_window.creation_spec = new_window.creation_spec._replace(spacing=tuple(opts.spacing)) if opts.color: new_window.creation_spec = new_window.creation_spec._replace(colors=tuple(opts.color)) - if startup_command_env_added and new_window.creation_spec.env: - def is_not_scmd(x: tuple[str, str]) -> bool: - return x[0] != 'KITTY_SI_RUN_COMMAND_AT_STARTUP' - new_window.creation_spec = new_window.creation_spec._replace(env=tuple(filter(is_not_scmd, new_window.creation_spec.env))) if spacing: patch_window_edges(new_window, spacing) tab.relayout() diff --git a/kitty/tabs.py b/kitty/tabs.py index 9be92fe0e..55c385a14 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -534,7 +534,8 @@ class Tab: # {{{ hold: bool = False, pass_fds: tuple[int, ...] = (), remote_control_fd: int = -1, - hold_after_ssh: bool = False + hold_after_ssh: bool = False, + startup_command_via_shell_integration: Sequence[str] = (), ) -> Child: check_for_suitability = True if cmd is None: @@ -584,7 +585,9 @@ 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, hold_after_ssh=hold_after_ssh) + 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, + startup_command_via_shell_integration=startup_command_via_shell_integration) ans.fork() return ans @@ -623,7 +626,8 @@ class Tab: # {{{ pass_fds: tuple[int, ...] = (), remote_control_fd: int = -1, next_to: Window | None = None, - hold_after_ssh: bool = False + hold_after_ssh: bool = False, + startup_command_via_shell_integration: Sequence[str] = (), ) -> Window: cs = WindowCreationSpec( use_shell=use_shell, cmd=cmd, has_stdin=bool(stdin), override_title=override_title, cwd_from=cwd_from, @@ -637,7 +641,8 @@ class Tab: # {{{ 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_after_ssh=hold_after_ssh + hold=hold, pass_fds=pass_fds, remote_control_fd=remote_control_fd, hold_after_ssh=hold_after_ssh, + startup_command_via_shell_integration=startup_command_via_shell_integration, ) window = Window( self, child, self.args, override_title=override_title,