From 6aa82d82ad40f147c8e07c150ad8abe4f0cd1133 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 13 Nov 2019 09:58:28 +0530 Subject: [PATCH] Start work on a unified interface for launching processes --- kitty/boss.py | 15 +++++- kitty/child.py | 26 ++++++--- kitty/config.py | 3 +- kitty/launch.py | 140 ++++++++++++++++++++++++++++++++++++++++++++++++ kitty/tabs.py | 13 ++--- 5 files changed, 182 insertions(+), 15 deletions(-) create mode 100644 kitty/launch.py diff --git a/kitty/boss.py b/kitty/boss.py index 1a2ebe9cf..6e2454dd5 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -1006,14 +1006,20 @@ class Boss: def _new_window(self, args, cwd_from=None): tab = self.active_tab if tab is not None: + allow_remote_control = False location = None if args and args[0].startswith('!'): location = args[0][1:].lower() args = args[1:] + if args and args[0] == '@': + args = args[1:] + allow_remote_control = True if args: - return tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from), location=location) + return tab.new_special_window( + self.args_to_special_window(args, cwd_from=cwd_from), + location=location, allow_remote_control=allow_remote_control) else: - return tab.new_window(cwd_from=cwd_from, location=location) + return tab.new_window(cwd_from=cwd_from, location=location, allow_remote_control=allow_remote_control) def new_window(self, *args): self._new_window(args) @@ -1025,6 +1031,11 @@ class Boss: cwd_from = w.child.pid_for_cwd if w is not None else None self._new_window(args, cwd_from=cwd_from) + def launch(self, *args): + from kitty.launch import parse_launch_args, launch + opts, args = parse_launch_args(args) + launch(self, opts, args) + def move_tab_forward(self): tm = self.active_tab_manager if tm is not None: diff --git a/kitty/child.py b/kitty/child.py index 6e7524cc2..032ab91a9 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -160,12 +160,8 @@ class Child: child_fd = pid = None forked = False - def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None): - self.allow_remote_control = False - if argv and argv[0] == '@': - self.allow_remote_control = True - if len(argv) > 1: - argv = argv[1:] + def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None, allow_remote_control=False): + self.allow_remote_control = allow_remote_control self.argv = argv if cwd_from is not None: try: @@ -271,6 +267,13 @@ class Child: except Exception: return list(self.argv) + @property + def foreground_cmdline(self): + try: + return cmdline_of_process(self.pid_for_cwd) or self.cmdline + except Exception: + return self.cmdline + @property def environ(self): try: @@ -296,3 +299,14 @@ class Child: def foreground_cwd(self): with suppress(Exception): return cwd_of_process(self.pid_for_cwd) or None + + @property + def foreground_environ(self): + try: + return environ_of_process(self.pid_for_cwd) + except Exception: + try: + return environ_of_process(self.pid) + except Exception: + pass + return {} diff --git a/kitty/config.py b/kitty/config.py index 308507b4c..fdac97d4b 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -50,7 +50,8 @@ func_with_args, args_funcs = key_func() @func_with_args( 'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window', - 'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd' + 'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd', + 'launch' ) def shlex_parse(func, rest): return func, to_cmdline(rest) diff --git a/kitty/launch.py b/kitty/launch.py new file mode 100644 index 000000000..2550a1889 --- /dev/null +++ b/kitty/launch.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2019, Kovid Goyal + + +from kitty.cli import parse_args + + +def options_spec(): + if not hasattr(options_spec, 'ans'): + OPTIONS = ''' +--window-title --title +The title to set for the new window. By default, title is controlled by the +child process. + + +--tab-title +The title for the new tab if launching in a new tab. By default, the title +of the actie window in the tab is used as the tab title. + + +--type +type=choices +default=window +choices=window,tab,os-window +Where to launch the child process, in a new kitty window in the current tab, +a new tab, or a new OS window. + + +--cwd +The working directory for the newly launched child. Use the special value +:code:`current` to use the working directory of the currently active window. + + +--env +type=list +Environment variables to set in the child process. Can be specified multiple +times to set different environment variables. +Syntax: :italic:`name=value`. + + +--copy-colors +type=bool-set +Set the colors of the newly created window to be the same as the colors in the +currently active window. + + +--copy-cmdline +type=bool-set +Ignore any specified command line and instead use the command line from the +currently active window. + + +--copy-env +type=bool-set +Copy the environment variables from the currently active window into the +newly launched child process. + + +--location +type=choices +default=last +choices=first,neighbor,last +Where to place the newly created window when it is added to a tab which +already has existing windows in it. + + +--allow-remote-control +type=bool-set +Programs running in this window can control kitty (if remote control is +enabled). Note that any program with the right level of permissions can still +write to the pipes of any other program on the same computer and therefore can +control kitty. It can, however, be useful to block programs running on other +computers (for example, over ssh) or as other users. + + +''' + options_spec.ans = OPTIONS + return options_spec.ans + + +def parse_launch_args(args): + args = list(args or ()) + try: + opts, args = parse_args(args=args, ospec=options_spec) + except SystemExit as e: + raise ValueError from e + return opts, args + + +def get_env(opts, active_child): + env = {} + if opts.copy_env and active_child: + env.update(active_child.foreground_environ) + for x in opts.env: + parts = x.split('=', 1) + if len(parts) == 2: + env[parts[0]] = parts[1] + return env + + +def launch(boss, opts, args): + if opts.type == 'tab': + tm = boss.active_tab_manager + tab = tm.new_tab(empty_tab=True) + if opts.tab_title: + tab.set_title(opts.tab_title) + elif opts.type == 'os-window': + oswid = boss.add_os_window() + tm = boss.os_window_map[oswid] + tab = tm.new_tab(empty_tab=True) + if opts.tab_title: + tab.set_title(opts.tab_title) + else: + tab = boss.active_tab + active = boss.active_window_for_cwd + active_child = getattr(active, 'child', None) + kw = { + 'env': get_env(opts, active_child) or None, + 'allow_remote_control': opts.allow_remote_control + } + if opts.cwd: + if opts.cwd == 'current': + if active_child: + kw['cwd_from'] = active_child.pid_for_cwd + else: + kw['cwd'] = opts.cwd + if opts.location != 'last': + kw['location'] = opts.location + if opts.window_title: + kw['override_title'] = opts.window_title + if opts.copy_colors and active: + kw['copy_colors_from'] = active + cmd = args or None + if opts.copy_cmdline and active_child: + cmd = active_child.foreground_cmdline + if cmd: + kw['cmd'] = cmd + + return tab.new_window(**kw) diff --git a/kitty/tabs.py b/kitty/tabs.py index 5b7cca176..074be4d63 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -236,7 +236,7 @@ class Tab: # {{{ if self.current_layout.remove_all_biases(): self.relayout() - def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None, env=None): + def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None, env=None, allow_remote_control=False): if cmd is None: if use_shell: cmd = resolved_shell(self.opts) @@ -252,7 +252,7 @@ class Tab: # {{{ except Exception: import traceback traceback.print_exc() - ans = Child(cmd, cwd or self.cwd, self.opts, stdin, fenv, cwd_from) + ans = Child(cmd, cwd or self.cwd, self.opts, stdin, fenv, cwd_from, allow_remote_control=allow_remote_control) ans.fork() return ans @@ -263,9 +263,10 @@ class Tab: # {{{ def new_window( self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None, env=None, location=None, - copy_colors_from=None + copy_colors_from=None, allow_remote_control=False ): - child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env) + child = self.launch_child( + use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env, allow_remote_control=allow_remote_control) window = Window(self, child, self.opts, self.args, override_title=override_title, copy_colors_from=copy_colors_from) if overlay_for is not None: overlaid = next(w for w in self.windows if w.id == overlay_for) @@ -276,8 +277,8 @@ class Tab: # {{{ self._add_window(window, location=location) return window - def new_special_window(self, special_window, location=None, copy_colors_from=None): - return self.new_window(False, *special_window, location=location, copy_colors_from=copy_colors_from) + def new_special_window(self, special_window, location=None, copy_colors_from=None, allow_remote_control=False): + return self.new_window(False, *special_window, location=location, copy_colors_from=copy_colors_from, allow_remote_control=allow_remote_control) def close_window(self): if self.windows: