diff --git a/kitty/boss.py b/kitty/boss.py index a3f889663..6864a6fd0 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -5,12 +5,13 @@ from gettext import gettext as _ from weakref import WeakValueDictionary -from .config import MINIMUM_FONT_SIZE, cached_values +from .config import MINIMUM_FONT_SIZE, cached_values, initial_window_size from .constants import set_boss, wakeup from .fast_data_types import ( - GLFW_KEY_DOWN, GLFW_KEY_UP, ChildMonitor, destroy_global_data, - destroy_sprite_map, get_clipboard_string, glfw_post_empty_event, - layout_sprite_map, toggle_fullscreen, viewport_for_window + GLFW_KEY_DOWN, GLFW_KEY_UP, ChildMonitor, create_os_window, + current_os_window, destroy_global_data, destroy_sprite_map, + get_clipboard_string, glfw_post_empty_event, layout_sprite_map, + mark_os_window_for_close, toggle_fullscreen, viewport_for_window ) from .fonts.render import prerender, resize_fonts, set_font_family from .keys import get_key_map, get_shortcut @@ -59,9 +60,8 @@ class Boss: def __init__(self, os_window_id, opts, args): self.window_id_map = WeakValueDictionary() - startup_session = create_session(opts, args) + self.os_window_map = {} self.cursor_blinking = True - self.window_is_focused = True self.shutting_down = False self.child_monitor = ChildMonitor( self.on_child_death, @@ -71,17 +71,42 @@ class Boss: set_font_family(opts) self.opts, self.args = opts, args initialize_renderer() - self.tab_manager = TabManager(opts, args) - self.tab_manager.init(startup_session) + startup_session = create_session(opts, args) + self.add_os_window(startup_session) + + def add_os_window(self, startup_session, os_window_id=None): + if os_window_id is None: + w, h = initial_window_size(self.opts) + os_window_id = create_os_window(w, h, self.args.cls) + tm = TabManager(os_window_id, self.opts, self.args, startup_session) + self.os_window_map[os_window_id] = tm def add_child(self, window): self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen) self.window_id_map[window.id] = window def on_child_death(self, window_id): - w = self.window_id_map.pop(window_id, None) - if w is not None: - w.on_child_death() + window = self.window_id_map.pop(window_id, None) + if window is None: + return + os_window_id = window.os_window_id + window.destroy() + tm = self.os_window_map.get(os_window_id) + if tm is None: + return + for tab in tm: + if window in tab: + break + else: + return + tab.remove_window(window) + if len(tab) == 0: + tm.remove(tab) + tab.destroy() + if len(tm) == 0: + if not self.shutting_down: + mark_os_window_for_close(os_window_id) + glfw_post_empty_event() def close_window(self, window=None): if window is None: @@ -103,12 +128,14 @@ class Boss: self.io_thread_started = True def activate_tab_at(self, os_window_id, x): - # WIN: Implement this - pass + tm = self.os_window_map.get(os_window_id) + if tm is not None: + tm.activate_tab_at(x) - def on_window_resize(self, window, w, h): - # WIN: Port this - self.tab_manager.resize() + def on_window_resize(self, os_window_id, w, h): + tm = self.os_window_map.get(os_window_id) + if tm is not None: + tm.resize() def increase_font_size(self): self.change_font_size( @@ -136,18 +163,22 @@ class Boss: prerender() for window in windows: window.screen.rescale_images(old_cell_width, old_cell_height) - self.resize_windows_after_font_size_change() - for window in windows: window.screen.refresh_sprite_positions() - self.tab_manager.refresh_sprite_positions() - - def resize_windows_after_font_size_change(self): - self.tab_manager.resize() + for tm in self.os_window_map.values(): + tm.resize() + tm.refresh_sprite_positions() glfw_post_empty_event() + @property + def active_tab_manager(self): + os_window_id = current_os_window() + return self.os_window_map.get(os_window_id) + @property def active_tab(self): - return self.tab_manager.active_tab + tm = self.active_tab_manager + if tm is not None: + return tm.active_tab @property def active_window(self): @@ -186,16 +217,20 @@ class Boss: for key_action in actions: self.dispatch_action(key_action) - def on_focus(self, window, focused): - # WIN: Port this - self.window_is_focused = focused - w = self.active_window - if w is not None: - w.focus_changed(focused) + def on_focus(self, os_window_id, focused): + tm = self.os_window_map.get(os_window_id) + if tm is not None: + w = tm.active_window + if w is not None: + w.focus_changed(focused) def on_os_window_closed(self, os_window_id, viewport_width, viewport_height): - # WIN: Implement this cached_values['window-size'] = viewport_width, viewport_height + for window_id in tuple(w.id for w in self.window_id_map.values() if getattr(w, 'os_window_id', None) == os_window_id): + self.window_id_map.pop(window_id, None) + tm = self.os_window_map.pop(os_window_id, None) + if tm is not None: + tm.destroy() def display_scrollback(self, data): if self.opts.scrollback_in_new_tab: @@ -225,32 +260,16 @@ class Boss: if url: open_url(url, self.opts.open_url_with) - def gui_close_window(self, window): - window.destroy() - for tab in self.tab_manager: - if window in tab: - break - else: - return - tab.remove_window(window) - if len(tab) == 0: - self.tab_manager.remove(tab) - tab.destroy() - if len(self.tab_manager) == 0: - if not self.shutting_down: - self.glfw_window.set_should_close(True) - glfw_post_empty_event() - def destroy(self): self.shutting_down = True self.child_monitor.shutdown() wakeup() self.child_monitor.join() - self.tab_manager.destroy() + for tm in self.os_window_map.values(): + tm.destroy() + self.os_window_map = {} destroy_sprite_map() destroy_global_data() - del self.tab_manager - del self.glfw_window def paste_to_active_window(self, text): if text: @@ -274,10 +293,14 @@ class Boss: set_primary_selection(text) def next_tab(self): - self.tab_manager.next_tab() + tm = self.active_tab_manager + if tm is not None: + tm.next_tab() def previous_tab(self): - self.tab_manager.next_tab(-1) + tm = self.active_tab_manager + if tm is not None: + tm.next_tab(-1) def args_to_special_window(self, args): args = list(args) @@ -311,7 +334,9 @@ class Boss: special_window = None if args: special_window = self.args_to_special_window(args) - self.tab_manager.new_tab(special_window=special_window) + tm = self.active_tab_manager + if tm is not None: + tm.new_tab(special_window=special_window) def new_window(self, *args): tab = self.active_tab @@ -322,14 +347,17 @@ class Boss: tab.new_window() def move_tab_forward(self): - self.tab_manager.move_tab(1) + tm = self.active_tab_manager + if tm is not None: + tm.move_tab(1) def move_tab_backward(self): - self.tab_manager.move_tab(-1) + tm = self.active_tab_manager + if tm is not None: + tm.move_tab(-1) def display_scrollback_in_new_tab(self, data): - self.tab_manager.new_tab( - special_window=SpecialWindow( + tm = self.active_tab_manager + if tm is not None: + tm.new_tab(special_window=SpecialWindow( self.opts.scrollback_pager, data, _('History'))) - - # }}} diff --git a/kitty/config.py b/kitty/config.py index b05c00522..8f50eaa32 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -422,3 +422,14 @@ def save_cached_values(): format(err), file=sys.stderr ) + + +def initial_window_size(opts): + w, h = opts.initial_window_width, opts.initial_window_height + if 'window-size' in cached_values and opts.remember_window_size: + ws = cached_values['window-size'] + try: + w, h = map(int, ws) + except Exception: + safe_print('Invalid cached window size, ignoring', file=sys.stderr) + return w, h diff --git a/kitty/main.py b/kitty/main.py index 9c6d0e5be..6f6aa324d 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -12,7 +12,7 @@ from gettext import gettext as _ from .boss import Boss from .config import ( - cached_values, load_cached_values, load_config, save_cached_values + initial_window_size, load_cached_values, load_config, save_cached_values ) from .constants import ( appname, defconf, isosx, iswayland, logo_data_file, str_version @@ -26,7 +26,7 @@ from .fonts.box_drawing import set_scale from .layout import all_layouts from .utils import ( detach, end_startup_notification, get_logical_dpi, - init_startup_notification, safe_print + init_startup_notification ) try: @@ -142,13 +142,7 @@ def run_app(opts, args): set_scale(opts.box_drawing_scale) set_options(opts, iswayland, args.debug_gl) load_cached_values() - w, h = opts.initial_window_width, opts.initial_window_height - if 'window-size' in cached_values and opts.remember_window_size: - ws = cached_values['window-size'] - try: - w, h = map(int, ws) - except Exception: - safe_print('Invalid cached window size, ignoring', file=sys.stderr) + w, h = initial_window_size() window_id = create_os_window(w, h, args.cls) startup_ctx = init_startup_notification(window_id) if isosx: diff --git a/kitty/state.c b/kitty/state.c index ea5c190c9..1f5ff66ca 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -341,6 +341,16 @@ PYWRAP1(viewport_for_window) { return Py_BuildValue("iiII", 400, 400, global_state.cell_width, global_state.cell_height); } +PYWRAP1(mark_os_window_for_close) { + id_type os_window_id; + int yes = 1; + PA("K|p", &os_window_id, &yes); + WITH_OS_WINDOW(os_window_id) + mark_os_window_for_close(os_window, yes ? true : false); + Py_RETURN_TRUE; + END_WITH_OS_WINDOW + Py_RETURN_FALSE; +} PYWRAP1(set_window_render_data) { #define A(name) &(d.name) @@ -402,6 +412,7 @@ THREE_ID_OBJ(update_window_title) THREE_ID(remove_window) PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); } PYWRAP1(add_window) { PyObject *title; id_type a, b; PA("KKO", &a, &b, &title); return PyLong_FromUnsignedLongLong(add_window(a, b, title)); } +PYWRAP0(current_os_window) { OSWindow *w = current_os_window(); if (!w) Py_RETURN_NONE; return PyLong_FromUnsignedLongLong(w->id); } TWO_ID(remove_tab) KI(set_active_tab) KKI(set_active_window) @@ -413,6 +424,7 @@ KK5I(add_borders_rect) #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} static PyMethodDef module_methods[] = { + MW(current_os_window, METH_NOARGS), MW(set_options, METH_O), MW(handle_for_window_id, METH_VARARGS), MW(set_logical_dpi, METH_VARARGS), @@ -429,6 +441,7 @@ static PyMethodDef module_methods[] = { MW(set_tab_bar_render_data, METH_VARARGS), MW(set_window_render_data, METH_VARARGS), MW(viewport_for_window, METH_O), + MW(mark_os_window_for_close, METH_VARARGS), MW(update_window_visibility, METH_VARARGS), MW(set_boss, METH_O), MW(set_display_state, METH_VARARGS), diff --git a/kitty/tabs.py b/kitty/tabs.py index 96f3ba2e8..2ead99bea 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -2,6 +2,7 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal +import weakref from collections import deque, namedtuple from functools import partial @@ -27,32 +28,32 @@ def SpecialWindow(cmd, stdin=None, override_title=None): class Tab: # {{{ - def __init__(self, os_window_id, opts, args, on_title_change, session_tab=None, special_window=None): - self.os_window_id = os_window_id - self.id = add_tab(os_window_id, self.id) + def __init__(self, tab_manager, session_tab=None, special_window=None): + self.tab_manager_ref = weakref.ref(tab_manager) + self.os_window_id = tab_manager.os_window_id + self.id = add_tab(self.os_window_id, self.id) if not self.id: - raise Exception('No OS window with id {} found, or tab counter has wrapped'.format(os_window_id)) - self.opts, self.args = opts, args + raise Exception('No OS window with id {} found, or tab counter has wrapped'.format(self.os_window_id)) + self.opts, self.args = tab_manager.opts, tab_manager.args self.name = getattr(session_tab, 'name', '') - self.on_title_change = on_title_change - self.enabled_layouts = list(getattr(session_tab, 'enabled_layouts', None) or opts.enabled_layouts) - self.borders = Borders(self.os_window_id, self.id, opts) + self.enabled_layouts = list(getattr(session_tab, 'enabled_layouts', None) or self.opts.enabled_layouts) + self.borders = Borders(self.os_window_id, self.id, self.opts) self.windows = deque() self.active_window_idx = 0 for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()): setattr(self, which + '_window', partial(self.nth_window, num=i)) if session_tab is None: - self.cwd = args.directory + self.cwd = self.args.directory sl = self.enabled_layouts[0] - self.current_layout = all_layouts[sl](opts, self.borders.border_width, self.windows) + self.current_layout = all_layouts[sl](self.opts, self.borders.border_width, self.windows) if special_window is None: self.new_window() else: self.new_special_window(special_window) else: - self.cwd = session_tab.cwd or args.directory + self.cwd = session_tab.cwd or self.args.directory l0 = session_tab.layout - self.current_layout = all_layouts[l0](opts, self.borders.border_width, self.windows) + self.current_layout = all_layouts[l0](self.opts, self.borders.border_width, self.windows) self.startup(session_tab) def startup(self, session_tab): @@ -70,7 +71,9 @@ class Tab: # {{{ def title_changed(self, window): if window is self.active_window: - self.on_title_change(window.title) + tm = self.tab_manager_ref() + if tm is not None: + tm.title_changed(window.title) def visible_windows(self): for w in self.windows: @@ -83,9 +86,10 @@ class Tab: # {{{ self.relayout_borders() def relayout_borders(self): - tm = get_boss().tab_manager - self.borders(self.windows, self.active_window, self.current_layout, - tm.blank_rects, self.current_layout.needs_window_borders and len(self.windows) > 1) + tm = self.tab_manager_ref() + if tm is not None: + self.borders(self.windows, self.active_window, self.current_layout, + tm.blank_rects, self.current_layout.needs_window_borders and len(self.windows) > 1) def next_layout(self): if len(self.opts.enabled_layouts) > 1: @@ -195,6 +199,8 @@ class Tab: # {{{ return window in self.windows def destroy(self): + for w in self.windows: + w.destroy() self.windows = deque() def __repr__(self): @@ -285,15 +291,24 @@ class TabBar: # {{{ class TabManager: # {{{ - def __init__(self, os_window_id, opts, args): + def __init__(self, os_window_id, opts, args, startup_session): self.os_window_id = os_window_id self.opts, self.args = opts, args self.tabs = [] self.tab_bar = TabBar(opts) - self.refresh_sprite_positions = self.tab_bar.screen.refresh_sprite_positions self.tab_bar.layout(*self.tab_bar_layout_data) self.active_tab_idx = 0 + for t in startup_session.tabs: + self._add_tab(Tab(self, session_tab=t)) + self._set_active_tab(max(0, min(startup_session.active_tab_idx, len(self.tabs) - 1))) + if len(self.tabs) > 1: + self.tabbar_visibility_changed() + self.update_tab_bar() + + def refresh_sprite_positions(self): + self.tab_bar.screen.refresh_sprite_positions() + def _add_tab(self, tab): self.tabs.append(tab) @@ -309,14 +324,6 @@ class TabManager: # {{{ self.resize(only_tabs=True) glfw_post_empty_event() - def init(self, startup_session): - for t in startup_session.tabs: - self._add_tab(Tab(self.os_window_id, self.opts, self.args, self.title_changed, t)) - self._set_active_tab(max(0, min(startup_session.active_tab_idx, len(self.tabs) - 1))) - if len(self.tabs) > 1: - self.tabbar_visibility_changed() - self.update_tab_bar() - def update_tab_bar(self): if len(self.tabs) > 1: self.tab_bar.update(self.tab_bar_data) @@ -362,7 +369,7 @@ class TabManager: # {{{ def new_tab(self, special_window=None): needs_resize = len(self.tabs) == 1 idx = len(self.tabs) - self._add_tab(Tab(self.os_window_id, self.opts, self.args, self.title_changed, special_window=special_window)) + self._add_tab(Tab(self, special_window=special_window)) self._set_active_tab(idx) self.update_tab_bar() if needs_resize: @@ -400,15 +407,9 @@ class TabManager: # {{{ def blank_rects(self): return self.tab_bar.blank_rects if len(self.tabs) > 1 else () - def render(self): - if len(self.tabs) < 2: - return - self.tab_bar.render() - def destroy(self): for t in self: t.destroy() self.tab_bar.destroy() del self.tab_bar - del self.refresh_sprite_positions # }}} diff --git a/kitty/window.py b/kitty/window.py index 498ec15bc..6e53009b9 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -136,16 +136,6 @@ class Window: def close(self): get_boss().close_window(self) - def on_child_death(self): - if self.destroyed: - return - self.destroyed = True - # Remove cycles so that screen is de-allocated immediately - boss = get_boss() - self.screen.reset_callbacks() - boss.gui_close_window(self) - self.screen = None - def send_text(self, *args): mode = keyboard_mode_name(self.screen) required_mode, text = args[-2:] @@ -246,7 +236,11 @@ class Window: return ''.join(self.screen.text_for_selection()) def destroy(self): - pass + self.destroyed = True + if self.screen is not None: + # Remove cycles so that screen is de-allocated immediately + self.screen.reset_callbacks() + self.screen = None def buffer_as_ansi(self): data = []