Implement startup sessions
This commit is contained in:
parent
60513fcbab
commit
33d58fd7bc
5 changed files with 157 additions and 24 deletions
27
README.md
27
README.md
|
|
@ -52,6 +52,33 @@ By default kitty looks for a config file in the OS
|
|||
config directory (usually `~/.config/kitty/kitty.conf` on linux) but you can pass
|
||||
a specific path via the `--config` option.
|
||||
|
||||
Startup Sessions
|
||||
-------------------
|
||||
|
||||
You can control the tabs, window layout, working directory, startup programs, etc. by creating a
|
||||
"session" file and using the `--session` command line flag. For example:
|
||||
|
||||
```
|
||||
# Set the window layout for the current tab
|
||||
layout tall
|
||||
# Set the working directory for the current tab
|
||||
cd ~
|
||||
# Create a window and run the specified command in it
|
||||
launch zsh
|
||||
launch vim
|
||||
launch irssi --profile x
|
||||
|
||||
# Create a new tab
|
||||
new_tab
|
||||
cd ~/somewhere
|
||||
# Set the layouts allowed in this tab
|
||||
enabled_layouts tall, stack
|
||||
layout stack
|
||||
launch zsh
|
||||
# Make the previous window the currently focused window
|
||||
focus
|
||||
launch emacs
|
||||
```
|
||||
|
||||
Resources on terminal behavior
|
||||
------------------------------------------
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
from .char_grid import cursor_shader, cell_shader
|
||||
from .constants import is_key_pressed
|
||||
from .keys import interpret_text_event, interpret_key_event, get_shortcut
|
||||
from .session import create_session
|
||||
from .shaders import Sprites, ShaderProgram
|
||||
from .tabs import TabManager
|
||||
from .timers import Timers
|
||||
|
|
@ -65,6 +66,7 @@ class Boss(Thread):
|
|||
|
||||
def __init__(self, glfw_window, opts, args):
|
||||
Thread.__init__(self, name='ChildMonitor')
|
||||
startup_session = create_session(opts, args)
|
||||
self.cursor_blinking = True
|
||||
self.glfw_window_title = None
|
||||
self.action_queue = Queue()
|
||||
|
|
@ -91,14 +93,13 @@ def __init__(self, glfw_window, opts, args):
|
|||
glfw_window.scroll_callback = self.on_mouse_scroll
|
||||
glfw_window.cursor_pos_callback = self.on_mouse_move
|
||||
glfw_window.window_focus_callback = self.on_focus
|
||||
self.tab_manager = TabManager(opts, args)
|
||||
self.tab_manager = TabManager(opts, args, startup_session)
|
||||
self.sprites = Sprites()
|
||||
self.cell_program = ShaderProgram(*cell_shader)
|
||||
self.cursor_program = ShaderProgram(*cursor_shader)
|
||||
self.borders_program = BordersProgram()
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||
self.sprites.do_layout(cell_size.width, cell_size.height)
|
||||
self.queue_action(self.active_tab.new_window, False)
|
||||
self.glfw_window.set_click_cursor(False)
|
||||
self.show_mouse_cursor()
|
||||
self.start_cursor_blink()
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ def option_parser():
|
|||
a('--replay-commands', default=None, help=_('Replay previously dumped commands'))
|
||||
a('--window-layout', default=None, choices=frozenset(all_layouts.keys()), help=_(
|
||||
'The window layout to use on startup'))
|
||||
a('--session', default=None, help=_(
|
||||
'Path to a file containing the startup session (tabs, windows, layout, programs)'))
|
||||
a('args', nargs=argparse.REMAINDER, help=_(
|
||||
'The remaining arguments are used to launch a program other than the default shell. Any further options are passed'
|
||||
' directly to the program being invoked.'
|
||||
|
|
|
|||
92
kitty/session.py
Normal file
92
kitty/session.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import shlex
|
||||
|
||||
from .config import to_layout_names
|
||||
from .constants import shell_path
|
||||
from .layout import all_layouts
|
||||
|
||||
|
||||
class Tab:
|
||||
|
||||
def __init__(self, opts):
|
||||
self.windows = []
|
||||
self.active_window_idx = 0
|
||||
self.enabled_layouts = opts.enabled_layouts
|
||||
self.layout = (self.enabled_layouts or ['tall'])[0]
|
||||
self.cwd = None
|
||||
|
||||
|
||||
class Session:
|
||||
|
||||
def __init__(self):
|
||||
self.tabs = []
|
||||
self.active_tab_idx = 0
|
||||
|
||||
def add_tab(self, opts):
|
||||
self.tabs.append(Tab(opts))
|
||||
|
||||
def set_layout(self, val):
|
||||
if val not in all_layouts:
|
||||
raise ValueError('{} is not a valid layout'.format(val))
|
||||
self.tabs[-1].layout = val
|
||||
|
||||
def add_window(self, cmd):
|
||||
if cmd:
|
||||
cmd = shlex.split(cmd) if isinstance(cmd, str) else cmd
|
||||
else:
|
||||
cmd = None
|
||||
self.tabs[-1].windows.append(cmd)
|
||||
|
||||
def focus(self):
|
||||
self.active_tab_idx = max(0, len(self.tabs) - 1)
|
||||
self.tabs[-1].active_window_idx = max(0, len(self.tabs.windows) - 1)
|
||||
|
||||
def set_enabled_layouts(self, raw):
|
||||
self.tabs[-1].enabled_layouts = to_layout_names(raw)
|
||||
|
||||
def set_cwd(self, val):
|
||||
self.tabs[-1].cwd = val
|
||||
|
||||
|
||||
def parse_session(raw, opts):
|
||||
ans = Session()
|
||||
for line in raw.splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
cmd, rest = line.partition(' ')[::2]
|
||||
cmd, rest = cmd.strip(), rest.strip()
|
||||
if cmd == 'new_tab':
|
||||
ans.add_tab(opts)
|
||||
elif cmd == 'layout':
|
||||
ans.set_layout(rest)
|
||||
elif cmd == 'launch':
|
||||
ans.add_window(rest)
|
||||
elif cmd == 'focus':
|
||||
ans.focus()
|
||||
elif cmd == 'enabled_layouts':
|
||||
ans.set_enabled_layouts(rest)
|
||||
elif cmd == 'cd':
|
||||
ans.set_cwd(rest)
|
||||
else:
|
||||
raise ValueError('Unknown command in session file: {}'.format(cmd))
|
||||
|
||||
|
||||
def create_session(opts, args):
|
||||
if args.session:
|
||||
with open(args.session) as f:
|
||||
return parse_session(f.read(), opts)
|
||||
ans = Session()
|
||||
if args.window_layout:
|
||||
if args.window_layout not in opts.enabled_layouts:
|
||||
opts.enabled_layouts.insert(0, args.window_layout)
|
||||
current_layout = args.window_layout
|
||||
else:
|
||||
current_layout = opts.enabled_layouts[0] if opts.enabled_layouts else 'tall'
|
||||
ans.add_tab(opts)
|
||||
ans.tabs[-1].layout = current_layout
|
||||
cmd = args.args or [shell_path]
|
||||
ans.add_window(cmd)
|
||||
return ans
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
from collections import deque
|
||||
|
||||
from .child import Child
|
||||
from .constants import get_boss, appname, shell_path, cell_size
|
||||
from .constants import get_boss, appname, shell_path, cell_size, queue_action
|
||||
from .fast_data_types import glfw_post_empty_event
|
||||
from .layout import all_layouts
|
||||
from .borders import Borders
|
||||
|
|
@ -14,19 +14,25 @@
|
|||
|
||||
class Tab:
|
||||
|
||||
def __init__(self, opts, args):
|
||||
def __init__(self, opts, args, session_tab=None):
|
||||
self.opts, self.args = opts, args
|
||||
self.enabled_layouts = opts.enabled_layouts
|
||||
self.enabled_layouts = list((session_tab or opts).enabled_layouts)
|
||||
self.borders = Borders(opts)
|
||||
self.windows = deque()
|
||||
if args.window_layout:
|
||||
if args.window_layout not in self.enabled_layouts:
|
||||
self.enabled_layouts.insert(0, args.window_layout)
|
||||
self.current_layout = all_layouts[args.window_layout]
|
||||
else:
|
||||
self.current_layout = all_layouts[self.enabled_layouts[0]]
|
||||
self.active_window_idx = 0
|
||||
self.current_layout = self.current_layout(opts, self.borders.border_width, self.windows)
|
||||
if session_tab is None:
|
||||
self.cwd = args.directory
|
||||
self.current_layout = self.enabled_layouts[0]
|
||||
queue_action(self.new_window)
|
||||
else:
|
||||
self.cwd = session_tab.cwd or args.directory
|
||||
self.current_layout = all_layouts[session_tab.layout](opts, self.borders.border_width, self.windows)
|
||||
queue_action(self.startup, session_tab)
|
||||
|
||||
def startup(self, session_tab):
|
||||
for cmd in session_tab.windows:
|
||||
self.new_window(cmd=cmd)
|
||||
self.active_window_idx = session_tab.active_window_idx
|
||||
|
||||
@property
|
||||
def is_visible(self):
|
||||
|
|
@ -52,24 +58,28 @@ def relayout(self):
|
|||
|
||||
def next_layout(self):
|
||||
if len(self.opts.enabled_layouts) > 1:
|
||||
idx = self.opts.enabled_layouts.index(self.current_layout.name)
|
||||
try:
|
||||
idx = self.opts.enabled_layouts.index(self.current_layout.name)
|
||||
except Exception:
|
||||
idx = -1
|
||||
nl = self.opts.enabled_layouts[(idx + 1) % len(self.opts.enabled_layouts)]
|
||||
self.current_layout = all_layouts[nl](self.opts, self.borders.border_width, self.windows)
|
||||
for w in self.windows:
|
||||
w.is_visible_in_layout = True
|
||||
self.relayout()
|
||||
|
||||
def launch_child(self, use_shell=False):
|
||||
if use_shell:
|
||||
cmd = [shell_path]
|
||||
else:
|
||||
cmd = self.args.args or [shell_path]
|
||||
ans = Child(cmd, self.args.directory, self.opts)
|
||||
def launch_child(self, use_shell=False, cmd=None):
|
||||
if cmd is None:
|
||||
if use_shell:
|
||||
cmd = [shell_path]
|
||||
else:
|
||||
cmd = self.args.args or [shell_path]
|
||||
ans = Child(cmd, self.cwd, self.opts)
|
||||
ans.fork()
|
||||
return ans
|
||||
|
||||
def new_window(self, use_shell=True):
|
||||
child = self.launch_child(use_shell=use_shell)
|
||||
def new_window(self, use_shell=True, cmd=None):
|
||||
child = self.launch_child(use_shell=use_shell, cmd=cmd)
|
||||
window = Window(self, child, self.opts, self.args)
|
||||
get_boss().add_child_fd(child.child_fd, window.read_ready, window.write_ready)
|
||||
self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx)
|
||||
|
|
@ -129,9 +139,10 @@ def render(self):
|
|||
|
||||
class TabManager:
|
||||
|
||||
def __init__(self, opts, args):
|
||||
def __init__(self, opts, args, startup_session):
|
||||
self.opts, self.args = opts, args
|
||||
self.tabs = [Tab(opts, args)]
|
||||
self.tabs = [Tab(opts, args, t) for t in startup_session.tabs]
|
||||
self.active_tab_idx = startup_session.active_tab_idx
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.tabs)
|
||||
|
|
@ -141,7 +152,7 @@ def __len__(self):
|
|||
|
||||
@property
|
||||
def active_tab(self):
|
||||
return self.tabs[0] if self.tabs else None
|
||||
return self.tabs[self.active_tab_idx] if self.tabs else None
|
||||
|
||||
@property
|
||||
def tab_bar_height(self):
|
||||
|
|
|
|||
Loading…
Reference in a new issue