kitty/kitty/launch.py
2020-04-24 19:27:22 +05:30

312 lines
10 KiB
Python

#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
from functools import lru_cache
from typing import Any, Dict, List, Optional, Sequence, Tuple
from .boss import Boss
from .child import Child
from .cli import parse_args
from .cli_stub import LaunchCLIOptions
from .constants import resolve_custom_file
from .fast_data_types import set_clipboard_string
from .tabs import Tab
from .utils import set_primary_selection
from .window import Watchers, Window
try:
from typing import TypedDict
except ImportError:
TypedDict = Dict[str, Any]
@lru_cache(maxsize=2)
def options_spec() -> str:
return '''
--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 active window in the tab is used as the tab title.
--type
type=choices
default=window
choices=window,tab,os-window,overlay,background,clipboard,primary
Where to launch the child process, in a new kitty window in the current tab,
a new tab, or a new OS window or an overlay over the current window.
Note that if the current window already has an overlay, then it will
open a new window. The value of background means the process will be
run in the background. The values clipboard and primary are meant
to work with :option:`launch --stdin-source` to copy data to the system
clipboard or primary selection.
--keep-focus
type=bool-set
Keep the focus on the currently active window instead of switching
to the newly opened 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,after,before,neighbor,last,vsplit,hsplit
Where to place the newly created window when it is added to a tab which
already has existing windows in it. :code:`after` and :code:`before` place the new
window before or after the active window. :code:`neighbor` is a synonym for :code:`after`.
Also applies to creating a new tab, where the value of :code:`after`
will cause the new tab to be placed next to the current tab instead of at the end.
The values of :code:`vsplit` and :code:`hsplit` are only used by the :code:`splits`
layout and control if the new window is placed in a vertical or horizontal split
with the currently active window.
--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.
--stdin-source
type=choices
default=none
choices=none,@selection,@screen,@screen_scrollback,@alternate,@alternate_scrollback
Pass the screen contents as :code:`STDIN` to the child process. @selection is
the currently selected text. @screen is the contents of the currently active
window. @screen_scrollback is the same as @screen, but includes the scrollback
buffer as well. @alternate is the secondary screen of the current active
window. For example if you run a full screen terminal application, the
secondary screen will be the screen you return to when quitting the
application.
--stdin-add-formatting
type=bool-set
When using :option:`launch --stdin-source` add formatting escape codes, without this
only plain text will be sent.
--stdin-add-line-wrap-markers
type=bool-set
When using :option:`launch --stdin-source` add a carriage return at every line wrap
location (where long lines are wrapped at screen edges). This is useful if you
want to pipe to program that wants to duplicate the screen layout of the
screen.
--marker
Create a marker that highlights text in the newly created window. The syntax is
the same as for the :code:`toggle_marker` map action (see :doc:`/marks`).
--os-window-class
Set the WM_CLASS property on X11 and the application id property on Wayland for
the newly created OS Window when using :option:`launch --type`=os-window.
Defaults to whatever is used by the parent kitty process, which in turn
defaults to :code:`kitty`.
--os-window-name
Set the WM_NAME property on X11 for the newly created OS Window when using
:option:`launch --type`=os-window. Defaults to :option:`launch --os-window-class`.
--watcher -w
type=list
Path to a python file. Appropriately named functions in this file will be called
for various events, such as when the window is resized or closed. See the section
on watchers in the launch command documentation :doc:`launch`. Relative paths are
resolved relative to the kitty config directory.
'''
def parse_launch_args(args: Optional[Sequence[str]] = None) -> Tuple[LaunchCLIOptions, List[str]]:
args = list(args or ())
try:
opts, args = parse_args(result_class=LaunchCLIOptions, args=args, ospec=options_spec)
except SystemExit as e:
raise ValueError from e
return opts, args
def get_env(opts: LaunchCLIOptions, active_child: Child) -> Dict[str, str]:
env: Dict[str, str] = {}
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 tab_for_window(boss: Boss, opts: LaunchCLIOptions, target_tab: Optional[Tab] = None) -> Optional[Tab]:
if opts.type == 'tab':
tm = boss.active_tab_manager
if tm:
tab: Optional[Tab] = tm.new_tab(empty_tab=True, location=opts.location)
if opts.tab_title and tab is not None:
tab.set_title(opts.tab_title)
else:
tab = None
elif opts.type == 'os-window':
oswid = boss.add_os_window(wclass=opts.os_window_class, wname=opts.os_window_name)
tm = boss.os_window_map[oswid]
tab = tm.new_tab(empty_tab=True)
if opts.tab_title and tab is not None:
tab.set_title(opts.tab_title)
else:
tab = target_tab or boss.active_tab
return tab
def load_watch_modules(opts: LaunchCLIOptions) -> Optional[Watchers]:
if not opts.watcher:
return None
import runpy
ans = Watchers()
for path in opts.watcher:
path = resolve_custom_file(path)
m = runpy.run_path(path, run_name='__kitty_watcher__')
w = m.get('on_close')
if callable(w):
ans.on_close.append(w)
w = m.get('on_resize')
if callable(w):
ans.on_resize.append(w)
return ans
class LaunchKwds(TypedDict):
allow_remote_control: bool
cwd_from: Optional[int]
cwd: Optional[str]
location: Optional[str]
override_title: Optional[str]
copy_colors_from: Optional[Window]
marker: Optional[str]
cmd: Optional[List[str]]
overlay_for: Optional[int]
stdin: Optional[bytes]
def launch(boss: Boss, opts: LaunchCLIOptions, args: List[str], target_tab: Optional[Tab] = None) -> Optional[Window]:
active = boss.active_window_for_cwd
active_child = getattr(active, 'child', None)
env = get_env(opts, active_child)
kw: LaunchKwds = {
'allow_remote_control': opts.allow_remote_control,
'cwd_from': None,
'cwd': None,
'location': None,
'override_title': opts.window_title or None,
'copy_colors_from': None,
'marker': opts.marker or None,
'cmd': None,
'overlay_for': None,
'stdin': None
}
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.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:
final_cmd: List[str] = []
for x in cmd:
if active and not opts.copy_cmdline:
if x == '@selection':
s = boss.data_for_at(which=x, window=active)
if s:
x = s
elif x == '@active-kitty-window-id':
x = str(active.id)
final_cmd.append(x)
kw['cmd'] = final_cmd
if opts.type == 'overlay' and active and not active.overlay_window_id:
kw['overlay_for'] = active.id
if opts.stdin_source != 'none':
q = str(opts.stdin_source)
if opts.stdin_add_formatting:
if q in ('@screen', '@screen_scrollback', '@alternate', '@alternate_scrollback'):
q = '@ansi_' + q[1:]
if opts.stdin_add_line_wrap_markers:
q += '_wrap'
penv, stdin = boss.process_stdin_source(window=active, stdin=q)
if stdin:
kw['stdin'] = stdin
if penv:
env.update(penv)
if opts.type == 'background':
cmd = kw['cmd']
if not cmd:
raise ValueError('The cmd to run must be specified when running a background process')
boss.run_background_process(cmd, cwd=kw['cwd'], cwd_from=kw['cwd_from'], env=env or None, stdin=kw['stdin'])
elif opts.type in ('clipboard', 'primary'):
stdin = kw.get('stdin')
if stdin is not None:
if opts.type == 'clipboard':
set_clipboard_string(stdin)
else:
set_primary_selection(stdin)
else:
tab = tab_for_window(boss, opts, target_tab)
if tab is not None:
watchers = load_watch_modules(opts)
new_window: Window = tab.new_window(env=env or None, watchers=watchers or None, **kw)
if opts.keep_focus and active:
boss.set_active_window(active, switch_os_window_if_needed=True)
return new_window
return None