Remote control launch: Fix the --copy-env option not copying current environment variables

Fixes #6724
This commit is contained in:
Kovid Goyal
2023-10-16 22:32:51 +05:30
parent d2026574b8
commit ddb121b418
5 changed files with 63 additions and 11 deletions

View File

@@ -62,6 +62,8 @@ Detailed list of changes
- Fix trailing bracket not ignored when detecting a multi-line URL with the trailing bracket as the first character on the last line (:iss:`6710`) - Fix trailing bracket not ignored when detecting a multi-line URL with the trailing bracket as the first character on the last line (:iss:`6710`)
- Remote control launch: Fix the ``--copy-env`` option not copying current environment variables (:iss:`6724`)
0.30.1 [2023-10-05] 0.30.1 [2023-10-05]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -306,6 +306,7 @@ json_field_types: Dict[str, str] = {
def go_field_type(json_field_type: str) -> str: def go_field_type(json_field_type: str) -> str:
json_field_type = json_field_type.partition('=')[0]
q = json_field_types.get(json_field_type) q = json_field_types.get(json_field_type)
if q: if q:
return q return q
@@ -324,6 +325,7 @@ class JSONField:
field_def = line.split(':', 1)[0] field_def = line.split(':', 1)[0]
self.required = False self.required = False
self.field, self.field_type = field_def.split('/', 1) self.field, self.field_type = field_def.split('/', 1)
self.field_type, self.special_parser = self.field_type.partition('=')[::2]
if self.field.endswith('+'): if self.field.endswith('+'):
self.required = True self.required = True
self.field = self.field[:-1] self.field = self.field[:-1]
@@ -370,14 +372,17 @@ def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) ->
if oq in option_map: if oq in option_map:
o = option_map[oq] o = option_map[oq]
used_options.add(oq) used_options.add(oq)
optstring = f'options_{name}.{o.go_var_name}'
if field.special_parser:
optstring = f'{field.special_parser}({optstring})'
if field.field_type == 'str': if field.field_type == 'str':
jc.append(f'payload.{field.struct_field_name} = escaped_string(options_{name}.{o.go_var_name})') jc.append(f'payload.{field.struct_field_name} = escaped_string({optstring})')
elif field.field_type == 'list.str': elif field.field_type == 'list.str':
jc.append(f'payload.{field.struct_field_name} = escape_list_of_strings(options_{name}.{o.go_var_name})') jc.append(f'payload.{field.struct_field_name} = escape_list_of_strings({optstring})')
elif field.field_type == 'dict.str': elif field.field_type == 'dict.str':
jc.append(f'payload.{field.struct_field_name} = escape_dict_of_strings(options_{name}.{o.go_var_name})') jc.append(f'payload.{field.struct_field_name} = escape_dict_of_strings({optstring})')
else: else:
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}') jc.append(f'payload.{field.struct_field_name} = {optstring}')
elif field.field in handled_fields: elif field.field in handled_fields:
pass pass
else: else:

View File

@@ -96,6 +96,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 via remote control a value of current uses the working directory
for the remote control invocation.
--env --env
@@ -322,10 +324,12 @@ def parse_launch_args(args: Optional[Sequence[str]] = None) -> LaunchSpec:
return LaunchSpec(opts, args) return LaunchSpec(opts, args)
def get_env(opts: LaunchCLIOptions, active_child: Optional[Child] = None) -> Dict[str, str]: def get_env(opts: LaunchCLIOptions, active_child: Optional[Child] = None, base_env: Optional[Dict[str,str]] = None) -> Dict[str, str]:
env: Dict[str, str] = {} env: Dict[str, str] = {}
if opts.copy_env and active_child: if opts.copy_env and active_child:
env.update(active_child.foreground_environ) env.update(active_child.foreground_environ)
if base_env is not None:
env.update(base_env)
for x in opts.env: for x in opts.env:
for k, v in parse_env(x, env): for k, v in parse_env(x, env):
env[k] = v env[k] = v
@@ -456,6 +460,7 @@ def _launch(
active: Optional[Window] = None, active: Optional[Window] = None,
is_clone_launch: str = '', is_clone_launch: str = '',
rc_from_window: Optional[Window] = None, rc_from_window: Optional[Window] = None,
base_env: Optional[Dict[str, str]] = None,
) -> Optional[Window]: ) -> Optional[Window]:
active = active or boss.active_window_for_cwd active = active or boss.active_window_for_cwd
if active: if active:
@@ -470,7 +475,7 @@ def _launch(
if opts.os_window_title == 'current': if opts.os_window_title == 'current':
tm = boss.active_tab_manager tm = boss.active_tab_manager
opts.os_window_title = get_os_window_title(tm.os_window_id) if tm else None opts.os_window_title = get_os_window_title(tm.os_window_id) if tm else None
env = get_env(opts, active_child) env = get_env(opts, active_child, base_env)
remote_control_restrictions: Optional[Dict[str, Sequence[str]]] = None remote_control_restrictions: Optional[Dict[str, Sequence[str]]] = None
if opts.allow_remote_control and opts.remote_control_password: if opts.allow_remote_control and opts.remote_control_password:
from kitty.options.utils import remote_control_password from kitty.options.utils import remote_control_password
@@ -633,12 +638,13 @@ def launch(
active: Optional[Window] = None, active: Optional[Window] = None,
is_clone_launch: str = '', is_clone_launch: str = '',
rc_from_window: Optional[Window] = None, rc_from_window: Optional[Window] = None,
base_env: Optional[Dict[str, str]] = None,
) -> Optional[Window]: ) -> Optional[Window]:
active = active or boss.active_window_for_cwd active = active or boss.active_window_for_cwd
if opts.keep_focus and active: if opts.keep_focus and active:
orig, active.ignore_focus_changes = active.ignore_focus_changes, True orig, active.ignore_focus_changes = active.ignore_focus_changes, True
try: try:
return _launch(boss, opts, args, target_tab, force_target_tab, active, is_clone_launch, rc_from_window) return _launch(boss, opts, args, target_tab, force_target_tab, active, is_clone_launch, rc_from_window, base_env)
finally: finally:
if opts.keep_focus and active: if opts.keep_focus and active:
active.ignore_focus_changes = orig active.ignore_focus_changes = orig

View File

@@ -2,7 +2,7 @@
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Dict, Optional
from kitty.cli_stub import LaunchCLIOptions from kitty.cli_stub import LaunchCLIOptions
from kitty.launch import launch as do_launch from kitty.launch import launch as do_launch
@@ -21,7 +21,7 @@ class Launch(RemoteCommand):
args+/list.str: The command line to run in the new window, as a list, use an empty list to run the default shell args+/list.str: The command line to run in the new window, as a list, use an empty list to run the default shell
match/str: The tab to open the new window in match/str: The tab to open the new window in
window_title/str: Title for the new window window_title/str: Title for the new window
cwd/str: Working directory for the new window cwd/str=copy_local_cwd: Working directory for the new window
env/list.str: List of environment variables of the form NAME=VALUE env/list.str: List of environment variables of the form NAME=VALUE
var/list.str: List of user variables of the form NAME=VALUE var/list.str: List of user variables of the form NAME=VALUE
tab_title/str: Title for the new tab tab_title/str: Title for the new tab
@@ -29,7 +29,7 @@ class Launch(RemoteCommand):
keep_focus/bool: Boolean indicating whether the current window should retain focus or not keep_focus/bool: Boolean indicating whether the current window should retain focus or not
copy_colors/bool: Boolean indicating whether to copy the colors from the current window copy_colors/bool: Boolean indicating whether to copy the colors from the current window
copy_cmdline/bool: Boolean indicating whether to copy the cmdline from the current window copy_cmdline/bool: Boolean indicating whether to copy the cmdline from the current window
copy_env/bool: Boolean indicating whether to copy the environ from the current window copy_env/list.str=copy_local_env: List of strings representing the local env vars
hold/bool: Boolean indicating whether to keep window open after cmd exits hold/bool: Boolean indicating whether to keep window open after cmd exits
location/choices.first.after.before.neighbor.last.vsplit.hsplit.split.default: Where in the tab to open the new window location/choices.first.after.before.neighbor.last.vsplit.hsplit.split.default: Where in the tab to open the new window
allow_remote_control/bool: Boolean indicating whether to allow remote control from the new window allow_remote_control/bool: Boolean indicating whether to allow remote control from the new window
@@ -83,17 +83,30 @@ instead of the active tab
default_opts = parse_launch_args()[0] default_opts = parse_launch_args()[0]
opts = LaunchCLIOptions() opts = LaunchCLIOptions()
for key, default_value in default_opts.__dict__.items(): for key, default_value in default_opts.__dict__.items():
if key == 'copy_env':
continue
val = payload_get(key) val = payload_get(key)
if val is None: if val is None:
val = default_value val = default_value
setattr(opts, key, val) setattr(opts, key, val)
ceval = payload_get('copy_env')
opts.copy_env = False
base_env: Optional[Dict[str, str]] = None
if ceval:
if isinstance(ceval, list):
base_env = {}
for x in ceval:
k, v = x.partition('=')[::2]
base_env[k] = v
elif isinstance(ceval, bool):
opts.copy_env = ceval
target_tab = None target_tab = None
tabs = self.tabs_for_match_payload(boss, window, payload_get) tabs = self.tabs_for_match_payload(boss, window, payload_get)
if tabs and tabs[0]: if tabs and tabs[0]:
target_tab = tabs[0] target_tab = tabs[0]
elif payload_get('type') not in ('background', 'os-window', 'tab', 'window'): elif payload_get('type') not in ('background', 'os-window', 'tab', 'window'):
return None return None
w = do_launch(boss, opts, payload_get('args') or [], target_tab=target_tab, rc_from_window=window) w = do_launch(boss, opts, payload_get('args') or [], target_tab=target_tab, rc_from_window=window, base_env=base_env)
return None if payload_get('no_response') else str(getattr(w, 'id', 0)) return None if payload_get('no_response') else str(getattr(w, 'id', 0))

26
tools/cmd/at/launch.go Normal file
View File

@@ -0,0 +1,26 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package at
import (
"fmt"
"os"
)
var _ = fmt.Print
func copy_local_env(copy_env bool) []string {
if copy_env {
return os.Environ()
}
return nil
}
func copy_local_cwd(copy_cwd string) string {
if copy_cwd == "current" {
if c, e := os.Getwd(); e == nil {
copy_cwd = c
}
}
return copy_cwd
}