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.
This commit is contained in:
Kovid Goyal 2025-08-19 10:37:05 +05:30
parent 1460a69ae9
commit 8451ad44b5
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
3 changed files with 30 additions and 27 deletions

View file

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

View file

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

View file

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