Allow easily injecting env vars from the login shell config into the env in which kitty runs child processes

Fixes #9042
This commit is contained in:
Kovid Goyal
2025-10-07 22:21:15 +05:30
parent fcccadc8f3
commit a9f80fe05b
5 changed files with 48 additions and 10 deletions

View File

@@ -137,6 +137,10 @@ Detailed list of changes
0.43.2 [future]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Allow kitty to read a specified set of environment variables from your
login shell at startup using the :opt:`env` directive in kitty.conf
(:iss:`9042`)
- Fix a regression in 0.43.0 that caused a black flicker when closing a tab in
the presence of a background image (:iss:`9060`)

View File

@@ -480,6 +480,16 @@ related to localization, such as :envvar:`LANG`, ``LC_*`` and loading of
configuration files such as ``XDG_*``, :envvar:`KITTY_CONFIG_DIRECTORY` and,
most importantly, ``PATH`` to locate binaries.
The simplest way to fix this is to have kitty load the environment variables
from your shell configuration at startup using the :opt:`env` directive,
adding the following to :file:`kitty.conf`::
env read_from_login_shell=PATH LANG LC_* XDG_* EDITOR VISUAL
This works for POSIX compliant shells and the fish shell. Note that it
does add significantly to kitty startup time, so use only if really necessary.
This feature was added in version ``0.43.2``.
To see the environment variables that kitty sees, you can add the following
mapping to :file:`kitty.conf`::

View File

@@ -67,6 +67,7 @@ from .utils import (
get_custom_window_icon,
log_error,
parse_os_window_state,
read_shell_environment,
safe_mtime,
startup_notification_handler,
)
@@ -478,6 +479,17 @@ def setup_environment(opts: Options, cli_opts: CLIOptions) -> None:
from_config_file = True
if cli_opts.listen_on:
cli_opts.listen_on = expand_listen_on(cli_opts.listen_on, from_config_file)
if vars := opts.env.pop('read_from_login_shell', ''):
import fnmatch
import re
senv = read_shell_environment(opts)
patterns = tuple(re.compile(fnmatch.translate(x.strip())) for x in vars.split() if x.strip())
if patterns:
for k, v in senv.items():
for pat in patterns:
if pat.match(k) is not None:
opts.env[k] = v
break
env = opts.env.copy()
ensure_kitty_in_path()
ensure_kitten_in_path()

View File

@@ -3254,7 +3254,9 @@ Changing this option by reloading the config is not supported.
'''
)
opt('+env', '',
opt(
'+env',
'',
option_type='env',
add_to_default=False,
long_text='''
@@ -3268,8 +3270,17 @@ recursively, for example::
env VAR2=${HOME}/${VAR1}/b
The value of :code:`VAR2` will be :code:`<path to home directory>/a/b`.
'''
)
Use the special
value :code:`read_from_login_shell` to have kitty read the specified variables from
your :opt:`login shell <shell>` configuration.
Useful if your shell startup files setup a bunch of environment variables that you want available to kitty and
in kitty session files. Each variable name is treated as a glob pattern to match. For example:
:code:`env read_from_login_shell=PATH LANG LC_* XDG_* EDITOR VISUAL`. Note that these variables are only
read after the configuration is fully processed, thus they are not available for recursive expansion and
they will override any variables set by other :opt:`env` directives.
''',
)
opt('+filter_notification', '', option_type='filter_notification', add_to_default=False, long_text='''
Specify rules to filter out notifications sent by applications running in kitty.

View File

@@ -748,12 +748,13 @@ def which(name: str, only_system: bool = False) -> str | None:
def read_resolved_shell_environment(shell: tuple[str, ...]) -> MappingProxyType[str, str]:
import subprocess
cmdline = list(shell)
if '-l' not in cmdline and '--login' not in shell:
if '-l' not in cmdline and '--login' not in cmdline:
cmdline += ['-l']
if '-i' not in shell and '--interactive' not in shell:
if '-i' not in cmdline and '--interactive' not in cmdline:
cmdline += ['-i']
is_fish = 'fish' in os.path.basename(cmdline[0]).lower()
cmd = 'command env -0' if is_fish else 'builtin command env -0'
q = os.path.basename(cmdline[0]).lower()
has_builtin = q in ('bash', 'zsh')
cmd = 'builtin command env -0' if has_builtin else 'command env -0'
ans: MappingProxyType[str, str] = MappingProxyType({})
from .child import openpty
@@ -764,7 +765,7 @@ def read_resolved_shell_environment(shell: tuple[str, ...]) -> MappingProxyType[
cmdline + ['-c', cmd], stdout=slave, stdin=slave, stderr=slave, start_new_session=True, close_fds=True,
preexec_fn=clear_handled_signals)
except FileNotFoundError:
log_error('Could not find shell to read environment')
log_error(f'Could not find shell {cmdline[0]} to read environment')
return ans
with os.fdopen(master, 'rb') as stdout, os.fdopen(slave, 'wb'):
raw = b''
@@ -781,7 +782,7 @@ def read_resolved_shell_environment(shell: tuple[str, ...]) -> MappingProxyType[
if ret is not None:
break
if ret is None:
log_error('Timed out waiting for shell to quit while reading shell environment')
log_error(f'Timed out waiting for shell {cmdline} to quit while reading shell environment')
p.kill()
elif ret == 0:
while True:
@@ -800,7 +801,7 @@ def read_resolved_shell_environment(shell: tuple[str, ...]) -> MappingProxyType[
env[k] = v
ans = MappingProxyType(env)
else:
log_error('Failed to run shell to read its environment')
log_error(f'Failed to run shell {cmdline} to read its environment')
return ans