diff --git a/kitty/boss.py b/kitty/boss.py index 77e74d6fd..a4a22f25f 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -8,14 +8,13 @@ import select import signal import struct import inspect -from collections import deque from functools import wraps from threading import Thread, current_thread from time import monotonic from queue import Queue, Empty from .constants import ( - viewport_size, set_tab_manager, wakeup, cell_size, MODIFIER_KEYS, + viewport_size, set_boss, wakeup, cell_size, MODIFIER_KEYS, main_thread, mouse_button_pressed, mouse_cursor_pos ) from .fast_data_types import ( @@ -29,7 +28,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 .shaders import Sprites, ShaderProgram -from .tabs import Tab +from .tabs import TabManager from .timers import Timers from .utils import handle_unix_signals @@ -82,7 +81,7 @@ class Boss(Thread): self.ui_timers = Timers() self.pending_ui_thread_calls = Queue() self.write_dispatch_map = {} - set_tab_manager(self) + set_boss(self) cell_size.width, cell_size.height = set_font_family(opts.font_family, opts.font_size) self.opts, self.args = opts, args self.glfw_window = glfw_window @@ -93,8 +92,7 @@ class Boss(Thread): 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.tabs = deque() - self.tabs.append(Tab(opts, args)) + self.tab_manager = TabManager(opts, args) self.sprites = Sprites() self.cell_program = ShaderProgram(*cell_shader) self.cursor_program = ShaderProgram(*cursor_shader) @@ -119,7 +117,7 @@ class Boss(Thread): glfw_post_empty_event() def __iter__(self): - yield from iter(self.tabs) + return iter(self.tab_manager) def iterwindows(self): for t in self: @@ -218,7 +216,7 @@ class Boss(Thread): def apply_pending_resize(self, w, h): viewport_size.width, viewport_size.height = w, h - for tab in self.tabs: + for tab in self.tab_manager: tab.relayout() self.resize_gl_viewport = True self.pending_resize = False @@ -226,7 +224,7 @@ class Boss(Thread): @property def active_tab(self): - return self.tabs[0] if self.tabs else None + return self.tab_manager.active_tab def is_tab_visible(self, tab): return self.active_tab is tab @@ -387,16 +385,16 @@ class Boss(Thread): if window.char_grid.buffer_id is not None: self.sprites.destroy_sprite_map(window.char_grid.buffer_id) window.char_grid.buffer_id = None - for tab in self.tabs: + for tab in self.tab_manager: if window in tab: break else: return tab.remove_window(window) if len(tab) == 0: - self.tabs.remove(tab) + self.tab_manager.remove(tab) tab.destroy() - if len(self.tabs) == 0: + if len(self.tab_manager) == 0: if not self.shutting_down: self.glfw_window.set_should_close(True) glfw_post_empty_event() @@ -408,9 +406,9 @@ class Boss(Thread): self.shutting_down = True wakeup() self.join() - for t in self.tabs: + for t in self.tab_manager: t.destroy() - del self.tabs + del self.tab_manager self.sprites.destroy() del self.sprites del self.glfw_window diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 2ba653457..661a8f519 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -8,7 +8,7 @@ from ctypes import addressof, memmove, sizeof from threading import Lock from .config import build_ansi_color_table -from .constants import tab_manager, viewport_size, cell_size, ScreenGeometry, GLuint +from .constants import get_boss, viewport_size, cell_size, ScreenGeometry, GLuint from .utils import get_logical_dpi, to_color, set_primary_selection, open_url from .fast_data_types import ( glUniform2ui, glUniform4f, glUniform1i, glUniform2f, glDrawArraysInstanced, @@ -281,7 +281,7 @@ class CharGrid: self.update_cell_data() def update_cell_data(self, force_full_refresh=False): - sprites = tab_manager().sprites + sprites = get_boss().sprites is_dirty = self.screen.is_dirty() with sprites.lock: cursor_changed, history_line_added_count = self.screen.update_cell_data( diff --git a/kitty/config.py b/kitty/config.py index 9bf6b491c..c977b67d4 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -62,6 +62,8 @@ named_keys = {"'": 'APOSTROPHE', ',': 'COMMA', '-': 'MINUS', '.': 'PERIOD', def parse_key(val, keymap): sc, action = val.partition(' ')[::2] + action = action.strip() + sc = sc.strip() if not sc or not action: return parts = sc.split('+') diff --git a/kitty/constants.py b/kitty/constants.py index e8fdeac6a..f845c8f05 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -49,20 +49,20 @@ class ViewportSize: return '(width={}, height={})'.format(self.width, self.height) -def tab_manager(): - return tab_manager.manager +def get_boss(): + return get_boss.boss -def set_tab_manager(m): - tab_manager.manager = m +def set_boss(m): + get_boss.boss = m def wakeup(): - os.write(tab_manager.manager.write_wakeup_fd, b'1') + os.write(get_boss.boss.write_wakeup_fd, b'1') def queue_action(func, *args): - tab_manager.manager.queue_action(func, *args) + get_boss.boss.queue_action(func, *args) is_key_pressed = defaultdict(lambda: False) diff --git a/kitty/layout.py b/kitty/layout.py index a19fdf2eb..fd9abca58 100644 --- a/kitty/layout.py +++ b/kitty/layout.py @@ -4,11 +4,11 @@ from itertools import islice -from .constants import WindowGeometry, viewport_size, cell_size, tab_manager +from .constants import WindowGeometry, viewport_size, cell_size, get_boss def available_height(): - return viewport_size.height - tab_manager().current_tab_bar_height + return viewport_size.height - get_boss().current_tab_bar_height def layout_dimension(length, cell_length, number_of_windows=1, border_length=0, left_align=False): diff --git a/kitty/tabs.py b/kitty/tabs.py index 7d2e0c86d..473ffc529 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -5,7 +5,7 @@ from collections import deque from .child import Child -from .constants import tab_manager, appname, shell_path +from .constants import get_boss, appname, shell_path from .fast_data_types import glfw_post_empty_event from .layout import all_layouts from .borders import Borders @@ -30,7 +30,7 @@ class Tab: @property def is_visible(self): - return tab_manager().is_tab_visible(self) + return get_boss().is_tab_visible(self) @property def active_window(self): @@ -71,7 +71,7 @@ class Tab: def new_window(self, use_shell=True): child = self.launch_child(use_shell=use_shell) window = Window(self, child, self.opts, self.args) - tab_manager().add_child_fd(child.child_fd, window.read_ready, window.write_ready) + 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) self.borders(self.windows, self.active_window, self.current_layout.needs_window_borders and len(self.windows) > 1) glfw_post_empty_event() @@ -123,4 +123,26 @@ class Tab: del self.windows def render(self): - self.borders.render(tab_manager().borders_program) + self.borders.render(get_boss().borders_program) + + +class TabManager: + + def __init__(self, opts, args): + self.opts, self.args = opts, args + self.tabs = [Tab(opts, args)] + + def __iter__(self): + return iter(self.tabs) + + def __len__(self): + return len(self.tabs) + + @property + def active_tab(self): + return self.tabs[0] if self.tabs else None + + def remove(self, tab): + ' Must be called in the GUI thread ' + self.tabs.remove(tab) + tab.destroy() diff --git a/kitty/window.py b/kitty/window.py index 427fdb940..a88d0f0d1 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -9,7 +9,7 @@ from functools import partial from time import monotonic from .char_grid import CharGrid -from .constants import wakeup, tab_manager, appname, WindowGeometry, is_key_pressed, mouse_button_pressed, cell_size +from .constants import wakeup, get_boss, appname, WindowGeometry, is_key_pressed, mouse_button_pressed, cell_size from .fast_data_types import ( BRACKETED_PASTE_START, BRACKETED_PASTE_END, Screen, read_bytes_dump, read_bytes, GLFW_MOD_SHIFT, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, @@ -73,7 +73,7 @@ class Window: return g.left <= x <= g.right and g.top <= y <= g.bottom def close(self): - tab_manager().close_window(self) + get_boss().close_window(self) def destroy(self): self.destroyed = True @@ -201,8 +201,8 @@ class Window: is_key_pressed[GLFW_KEY_LEFT_SHIFT] or is_key_pressed[GLFW_KEY_RIGHT_SHIFT]) x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top) self.last_mouse_cursor_pos = x, y - tm = tab_manager() - tm.queue_ui_action(tab_manager().change_mouse_cursor, self.char_grid.has_url_at(x, y)) + tm = get_boss() + tm.queue_ui_action(get_boss().change_mouse_cursor, self.char_grid.has_url_at(x, y)) if send_event: x, y = self.char_grid.cell_for_pos(x, y) if x is not None: @@ -215,11 +215,11 @@ class Window: self.char_grid.update_drag(None, x, y) margin = cell_size.height // 2 if y <= margin or y >= self.geometry.bottom - margin: - tab_manager().timers.add(0.02, self.drag_scroll) + get_boss().timers.add(0.02, self.drag_scroll) def drag_scroll(self): x, y = self.last_mouse_cursor_pos - tm = tab_manager() + tm = get_boss() margin = cell_size.height // 2 if y <= margin or y >= self.geometry.bottom - margin: self.scroll_line_up() if y < 50 else self.scroll_line_down() @@ -272,7 +272,7 @@ class Window: def copy_to_clipboard(self): text = self.char_grid.text_for_selection() if text: - tm = tab_manager() + tm = get_boss() tm.queue_ui_action(tm.glfw_window.set_clipboard_string, text) def scroll_line_up(self):