diff --git a/kitty/boss.py b/kitty/boss.py index 53a33e76e..d812c08bd 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -365,9 +365,6 @@ class Boss: self.glfw_window.set_title(self.glfw_window_title) if isosx: cocoa_update_title(self.glfw_window_title) - for window in tab.visible_windows(): - if not window.needs_layout: - window.char_grid.render_cells() active = self.active_window if active is not None: draw_cursor = True @@ -383,7 +380,7 @@ class Boss: active.char_grid.render_cursor(self.window_is_focused) def gui_close_window(self, window): - window.char_grid.destroy() + window.destroy() for tab in self.tab_manager: if window in tab: break diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 40c69ef17..299a26555 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -10,8 +10,7 @@ from .config import build_ansi_color_table from .constants import ScreenGeometry, cell_size, viewport_size from .fast_data_types import ( CELL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK, CURSOR_PROGRAM, CURSOR_UNDERLINE, - compile_program, create_cell_vao, draw_cells, draw_cursor, - init_cell_program, init_cursor_program, remove_vao + compile_program, draw_cursor, init_cell_program, init_cursor_program ) from .rgb import to_color from .utils import ( @@ -42,16 +41,11 @@ def calculate_gl_geometry(window_geometry, viewport_width, viewport_height, cell return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy) -def render_cells(vao_id, sg, screen): - draw_cells(vao_id, sg.xstart, sg.ystart, sg.dx, sg.dy, screen) - - class CharGrid: url_pat = re.compile('(?:http|https|file|ftp)://\S+', re.IGNORECASE) def __init__(self, screen, opts): - self.vao_id = create_cell_vao() self.screen_reversed = False self.screen = screen self.opts = opts @@ -63,16 +57,9 @@ class CharGrid: self.default_cursor = Cursor(0, 0, opts.cursor_shape, opts.cursor_blink_interval > 0) self.opts = opts - def destroy(self): - if self.vao_id is not None: - remove_vao(self.vao_id) - self.vao_id = None - def update_position(self, window_geometry): - self.screen_geometry = calculate_gl_geometry(window_geometry, viewport_size.width, viewport_size.height, cell_size.width, cell_size.height) - - def resize(self, window_geometry): - self.update_position(window_geometry) + self.screen_geometry = sg = calculate_gl_geometry(window_geometry, viewport_size.width, viewport_size.height, cell_size.width, cell_size.height) + return sg def change_colors(self, changes): dirtied = False @@ -172,9 +159,6 @@ class CharGrid: def text_for_selection(self): return ''.join(self.screen.text_for_selection()) - def render_cells(self): - render_cells(self.vao_id, self.screen_geometry, self.screen) - def render_cursor(self, is_focused): if not self.screen.cursor_visible or self.screen.scrolled_by: return diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index b5dfbccc6..38fc8fee9 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -15,7 +15,7 @@ extern int pthread_setname_np(const char *name); #include #undef _GNU_SOURCE #endif -#include "data-types.h" +#include "state.h" #include #include #include @@ -30,6 +30,7 @@ extern int pthread_setname_np(const char *name); #define EXTRA_FDS 2 +extern GlobalState global_state; static void (*parse_func)(Screen*, PyObject*); typedef struct { @@ -436,6 +437,16 @@ render(ChildMonitor *self, double *timeout) { #define TD global_state.tab_bar_render_data if (TD.screen && global_state.num_tabs > 1) draw_cells(TD.vao_idx, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen); #undef TD + if (global_state.num_tabs) { + Tab *tab = global_state.tabs + global_state.active_tab; + for (size_t i = 0; i < tab->num_windows; i++) { + Window *w = tab->windows + i; +#define WD w->render_data + if (w->visible && WD.screen) draw_cells(WD.vao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen); +#undef WD + } + } + ret = PyObject_CallFunctionObjArgs(self->render_func, NULL); if (ret == NULL) { PyErr_Print(); return false; } else Py_DECREF(ret); diff --git a/kitty/data-types.h b/kitty/data-types.h index fb63e4652..db01f36a4 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -288,33 +288,6 @@ typedef struct { } ChildMonitor; PyTypeObject ChildMonitor_Type; -typedef struct { - double visual_bell_duration; - bool enable_audio_bell; -} Options; - -typedef struct { - unsigned int id; -} Tab; - -typedef struct { - ssize_t vao_idx; - float xstart, ystart, dx, dy; - Screen *screen; -} ScreenRenderData; - -typedef struct { - Options opts; - - Tab tabs[MAX_CHILDREN]; - unsigned int active_tab, num_tabs; - ScreenRenderData tab_bar_render_data; -} GlobalState; - -#ifndef IS_STATE -extern GlobalState global_state; -#endif - #define clear_sprite_position(cell) (cell).sprite_x = 0; (cell).sprite_y = 0; (cell).sprite_z = 0; #define left_shift_line(line, at, num) \ @@ -326,11 +299,6 @@ extern GlobalState global_state; clear_sprite_position((line)->cells[(at)]); \ } -typedef void (*draw_borders_func)(); -extern draw_borders_func draw_borders; -typedef void (*draw_cells_func)(ssize_t, float, float, float, float, Screen *); -extern draw_cells_func draw_cells; - // Global functions Line* alloc_line(); Cursor* alloc_cursor(); diff --git a/kitty/layout.py b/kitty/layout.py index e786ee3f8..2d80d1f6f 100644 --- a/kitty/layout.py +++ b/kitty/layout.py @@ -122,14 +122,14 @@ class Stack(Layout): def set_active_window(self, windows, active_window_idx): for i, w in enumerate(windows): - w.is_visible_in_layout = i == active_window_idx + w.set_visible_in_layout(i, i == active_window_idx) def __call__(self, windows, active_window_idx): self.blank_rects = [] wg = layout_single_window(self.margin_width, self.padding_width) for i, w in enumerate(windows): - w.is_visible_in_layout = i == active_window_idx - w.set_geometry(wg) + w.set_visible_in_layout(i, i == active_window_idx) + w.set_geometry(i, wg) if w.is_visible_in_layout: self.blank_rects = blank_rects_for_window(w) @@ -142,7 +142,7 @@ class Tall(Layout): self.blank_rects = br = [] if len(windows) == 1: wg = layout_single_window(self.margin_width, self.padding_width) - windows[0].set_geometry(wg) + windows[0].set_geometry(0, wg) self.blank_rects = blank_rects_for_window(windows[0]) return xlayout = layout_dimension( @@ -152,14 +152,14 @@ class Tall(Layout): ystart, ynum = next(layout_dimension( available_height(), cell_size.height, 1, self.border_width, left_align=True, margin_length=self.margin_width, padding_length=self.padding_width)) - windows[0].set_geometry(window_geometry(xstart, xnum, ystart, ynum)) + windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum)) vh = available_height() xstart, xnum = next(xlayout) ylayout = layout_dimension( available_height(), cell_size.height, len(windows) - 1, self.border_width, left_align=True, margin_length=self.margin_width, padding_length=self.padding_width) - for w, (ystart, ynum) in zip(islice(windows, 1, None), ylayout): - w.set_geometry(window_geometry(xstart, xnum, ystart, ynum)) + for i, (w, (ystart, ynum)) in enumerate(zip(islice(windows, 1, None), ylayout)): + w.set_geometry(i + 1, window_geometry(xstart, xnum, ystart, ynum)) left_blank_rect(windows[0], br, vh), top_blank_rect(windows[0], br, vh), right_blank_rect(windows[-1], br, vh) br.append(Rect(windows[0].geometry.right, 0, windows[1].geometry.left, vh)) br.append(Rect(0, windows[0].geometry.bottom, windows[0].geometry.right, vh)) diff --git a/kitty/screen.c b/kitty/screen.c index 04a7237be..c73f28210 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -11,7 +11,7 @@ #define EXTRA_INIT PyModule_AddIntMacro(module, SCROLL_LINE); PyModule_AddIntMacro(module, SCROLL_PAGE); PyModule_AddIntMacro(module, SCROLL_FULL); -#include "data-types.h" +#include "state.h" #include "lineops.h" #include "screen.h" #include @@ -23,6 +23,7 @@ #include "modes.h" #include "wcwidth9.h" +extern GlobalState global_state; static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true}; static Selection EMPTY_SELECTION = {0}; diff --git a/kitty/shaders.c b/kitty/shaders.c index 33948ebd0..7385339f4 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -5,7 +5,7 @@ * Distributed under terms of the GPL3 license. */ -#include "data-types.h" +#include "state.h" #include "screen.h" #include "sprites.h" #ifdef __APPLE__ diff --git a/kitty/state.c b/kitty/state.c index 238c03985..907e7d309 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -5,24 +5,31 @@ * Distributed under terms of the GPL3 license. */ -#define IS_STATE -#include "data-types.h" +#include "state.h" GlobalState global_state = {{0}}; static const Tab EMPTY_TAB = {0}; +static const Window EMPTY_WINDOW = {0}; #define ensure_can_add(array, count, msg) if (count >= sizeof(array)/sizeof(array[0]) - 1) fatal(msg); -#define REMOVER(array, qid, count, empty, structure) { \ +#define noop(...) +#define REMOVER(array, qid, count, empty, structure, destroy) { \ size_t capacity = sizeof(array)/sizeof(array[0]); \ for (size_t i = 0; i < count; i++) { \ if (array[i].id == qid) { \ + destroy(array[i]); \ array[i] = empty; \ size_t num_to_right = capacity - count - 1; \ if (num_to_right) memmove(array + i, array + i + 1, num_to_right * sizeof(structure)); \ (count)--; \ } \ }} +#define WITH_TAB(tab_id) \ + for (size_t t = 0; t < global_state.num_tabs; t++) { \ + if (global_state.tabs[t].id == tab_id) { \ + Tab *tab = global_state.tabs + t; +#define END_WITH_TAB break; }} static inline void add_tab(unsigned int id) { @@ -33,15 +40,43 @@ add_tab(unsigned int id) { } static inline void -remove_tab(unsigned int id) { - REMOVER(global_state.tabs, id, global_state.num_tabs, EMPTY_TAB, Tab); +add_window(unsigned int tab_id, unsigned int id) { + WITH_TAB(tab_id); + ensure_can_add(tab->windows, tab->num_windows, "Too many children (add_window)"); + tab->windows[tab->num_windows] = EMPTY_WINDOW; + tab->windows[tab->num_windows].id = id; + tab->windows[tab->num_windows].visible = true; + tab->num_windows++; + END_WITH_TAB; } +static inline void +remove_tab(unsigned int id) { + REMOVER(global_state.tabs, id, global_state.num_tabs, EMPTY_TAB, Tab, noop); +} + +static inline void +remove_window(unsigned int tab_id, unsigned int id) { + WITH_TAB(tab_id); +#define destroy_window(w) Py_CLEAR(w.render_data.screen) + REMOVER(tab->windows, id, tab->num_windows, EMPTY_WINDOW, Window, destroy_window); +#undef destroy_window + END_WITH_TAB; +} + + static inline void set_active_tab(unsigned int idx) { global_state.active_tab = idx; } +static inline void +set_active_window(unsigned int tab_id, unsigned int idx) { + WITH_TAB(tab_id); + tab->active_window = idx; + END_WITH_TAB; +} + static inline void swap_tabs(unsigned int a, unsigned int b) { Tab t = global_state.tabs[b]; @@ -49,6 +84,15 @@ swap_tabs(unsigned int a, unsigned int b) { global_state.tabs[a] = t; } +static inline void +swap_windows(unsigned int tab_id, unsigned int a, unsigned int b) { + WITH_TAB(tab_id); + Window w = tab->windows[b]; + tab->windows[b] = tab->windows[a]; + tab->windows[a] = w; + END_WITH_TAB; +} + // Python API {{{ #define PYWRAP0(name) static PyObject* py##name(PyObject UNUSED *self) #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) @@ -56,6 +100,7 @@ swap_tabs(unsigned int a, unsigned int b) { #define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL; #define ONE_UINT(name) PYWRAP1(name) { name((unsigned int)PyLong_AsUnsignedLong(args)); Py_RETURN_NONE; } #define TWO_UINT(name) PYWRAP1(name) { unsigned int a, b; PA("II", &a, &b); name(a, b); Py_RETURN_NONE; } +#define THREE_UINT(name) PYWRAP1(name) { unsigned int a, b, c; PA("III", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; } PYWRAP1(set_options) { #define S(name, convert) { PyObject *ret = PyObject_GetAttrString(args, #name); if (ret == NULL) return NULL; global_state.opts.name = convert(ret); Py_DECREF(ret); } @@ -74,15 +119,44 @@ PYWRAP1(set_tab_bar_render_data) { #undef A } +PYWRAP1(set_window_render_data) { +#define A(name) &(d.name) + unsigned int window_idx, tab_id; + ScreenRenderData d = {0}; + PA("IIiffffO", &tab_id, &window_idx, A(vao_idx), A(xstart), A(ystart), A(dx), A(dy), A(screen)); + + WITH_TAB(tab_id); + Py_CLEAR(tab->windows[window_idx].render_data.screen); + tab->windows[window_idx].render_data = d; + Py_INCREF(tab->windows[window_idx].render_data.screen); + END_WITH_TAB; + Py_RETURN_NONE; +#undef A +} + +PYWRAP1(update_window_visibility) { + unsigned int window_idx, tab_id; + int visible; + PA("IIp", &tab_id, &window_idx, &visible); + WITH_TAB(tab_id); + tab->windows[window_idx].visible = visible & 1; + END_WITH_TAB; + Py_RETURN_NONE; +} + PYWRAP0(destroy_global_data) { Py_CLEAR(global_state.tab_bar_render_data.screen); Py_RETURN_NONE; } ONE_UINT(add_tab) +TWO_UINT(add_window) ONE_UINT(remove_tab) +TWO_UINT(remove_window) ONE_UINT(set_active_tab) +TWO_UINT(set_active_window) TWO_UINT(swap_tabs) +THREE_UINT(swap_windows) #define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL} #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} @@ -90,10 +164,16 @@ TWO_UINT(swap_tabs) static PyMethodDef module_methods[] = { MW(set_options, METH_O), MW(add_tab, METH_O), + MW(add_window, METH_VARARGS), MW(remove_tab, METH_O), + MW(remove_window, METH_VARARGS), MW(set_active_tab, METH_O), + MW(set_active_window, METH_VARARGS), MW(swap_tabs, METH_VARARGS), + MW(swap_windows, METH_VARARGS), MW(set_tab_bar_render_data, METH_VARARGS), + MW(set_window_render_data, METH_VARARGS), + MW(update_window_visibility, METH_VARARGS), MW(destroy_global_data, METH_NOARGS), {NULL, NULL, 0, NULL} /* Sentinel */ diff --git a/kitty/state.h b/kitty/state.h new file mode 100644 index 000000000..22b0a092d --- /dev/null +++ b/kitty/state.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once +#include "data-types.h" + +typedef struct { + double visual_bell_duration; + bool enable_audio_bell; +} Options; + +typedef struct { + ssize_t vao_idx; + float xstart, ystart, dx, dy; + Screen *screen; +} ScreenRenderData; + +typedef struct { + unsigned int id; + bool visible; + ScreenRenderData render_data; +} Window; + +typedef struct { + unsigned int id, active_window, num_windows; + Window windows[MAX_CHILDREN]; +} Tab; + +typedef struct { + Options opts; + + Tab tabs[MAX_CHILDREN]; + unsigned int active_tab, num_tabs; + ScreenRenderData tab_bar_render_data; +} GlobalState; + +typedef void (*draw_borders_func)(); +extern draw_borders_func draw_borders; +typedef void (*draw_cells_func)(ssize_t, float, float, float, float, Screen *); +extern draw_cells_func draw_cells; diff --git a/kitty/tabs.py b/kitty/tabs.py index 7d387c3c9..08528a0e0 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -14,8 +14,9 @@ from .constants import ( WindowGeometry, appname, cell_size, get_boss, shell_path, viewport_size ) from .fast_data_types import ( - DECAWM, Screen, add_tab, create_cell_vao, glfw_post_empty_event, - remove_tab, set_active_tab, swap_tabs, set_tab_bar_render_data + DECAWM, Screen, add_tab, add_window, create_cell_vao, + glfw_post_empty_event, remove_tab, remove_window, set_active_tab, + set_active_window, set_tab_bar_render_data, swap_tabs, swap_windows ) from .layout import Rect, all_layouts from .utils import color_as_int @@ -36,6 +37,7 @@ class Tab: # {{{ def __init__(self, opts, args, on_title_change, session_tab=None, special_window=None): global borders self.id = next(tab_counter) + add_tab(self.id) self.opts, self.args = opts, args self.name = getattr(session_tab, 'name', '') self.on_title_change = on_title_change @@ -104,8 +106,8 @@ class Tab: # {{{ idx = -1 nl = self.opts.enabled_layouts[(idx + 1) % len(self.opts.enabled_layouts)] self.current_layout = all_layouts[nl](self.opts, borders.border_width, self.windows) - for w in self.windows: - w.is_visible_in_layout = True + for i, w in enumerate(self.windows): + w.set_visible_in_layout(i, True) self.relayout() def launch_child(self, use_shell=False, cmd=None, stdin=None): @@ -125,7 +127,9 @@ class Tab: # {{{ window.title = window.override_title = override_title # Must add child before laying out so that resize_pty succeeds get_boss().add_child(window) + add_window(self.id, window.id) self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx) + set_active_window(self.id, self.active_window_idx) self.relayout_borders() glfw_post_empty_event() return window @@ -138,7 +142,9 @@ class Tab: # {{{ self.remove_window(self.windows[self.active_window_idx]) def remove_window(self, window): + remove_window(self.id, window.id) self.active_window_idx = self.current_layout.remove_window(self.windows, window, self.active_window_idx) + set_active_window(self.id, self.active_window_idx) self.relayout_borders() glfw_post_empty_event() @@ -146,6 +152,7 @@ class Tab: # {{{ if idx != self.active_window_idx: self.current_layout.set_active_window(self.windows, idx) self.active_window_idx = idx + set_active_window(self.id, self.active_window_idx) self.relayout_borders() glfw_post_empty_event() @@ -163,6 +170,7 @@ class Tab: # {{{ def _next_window(self, delta=1): if len(self.windows) > 1: self.active_window_idx = self.current_layout.next_window(self.windows, self.active_window_idx, delta) + set_active_window(self.id, self.active_window_idx) self.relayout_borders() glfw_post_empty_event() @@ -177,7 +185,9 @@ class Tab: # {{{ idx = self.active_window_idx nidx = (idx + len(self.windows) + delta) % len(self.windows) self.windows[nidx], self.windows[idx] = self.windows[idx], self.windows[nidx] + swap_windows(self.id, nidx, idx) self.active_window_idx = nidx + set_active_window(self.id, self.active_window_idx) self.relayout() def move_window_to_top(self): @@ -295,7 +305,6 @@ class TabManager: # {{{ self.active_tab_idx = 0 def _add_tab(self, tab): - add_tab(tab.id) self.tabs.append(tab) def _remove_tab(self, tab): diff --git a/kitty/window.py b/kitty/window.py index 09512e75c..dfd048191 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -18,7 +18,9 @@ from .fast_data_types import ( GLFW_KEY_LEFT_SHIFT, GLFW_KEY_RIGHT_SHIFT, GLFW_KEY_UP, GLFW_MOD_SHIFT, GLFW_MOUSE_BUTTON_1, GLFW_MOUSE_BUTTON_4, GLFW_MOUSE_BUTTON_5, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE, MOTION_MODE, - SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen, glfw_post_empty_event + SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen, create_cell_vao, + glfw_post_empty_event, remove_vao, set_window_render_data, + update_window_visibility ) from .keys import get_key_map from .mouse import DRAG, MOVE, PRESS, RELEASE, encode_mouse_event @@ -45,6 +47,8 @@ class Window: def __init__(self, tab, child, opts, args): self.id = next(window_counter) + self.vao_id = create_cell_vao() + self.tab_id = tab.id self.tabref = weakref.ref(tab) self.override_title = None self.last_mouse_cursor_pos = 0, 0 @@ -53,7 +57,7 @@ class Window: self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0) self.needs_layout = True self.title = appname - self._is_visible_in_layout = True + self.is_visible_in_layout = True self.child, self.opts = child, opts self.start_visual_bell_at = None self.screen = Screen(self, 24, 80, opts.scrollback_lines) @@ -63,15 +67,11 @@ class Window: def __repr__(self): return 'Window(title={}, id={})'.format(self.title, self.id) - @property - def is_visible_in_layout(self): - return self._is_visible_in_layout - - @is_visible_in_layout.setter - def is_visible_in_layout(self, val): + def set_visible_in_layout(self, window_idx, val): val = bool(val) - if val != self._is_visible_in_layout: - self._is_visible_in_layout = val + if val is not self.is_visible_in_layout: + self.is_visible_in_layout = val + update_window_visibility(self.tab_id, window_idx, val) if val: self.refresh() @@ -79,7 +79,7 @@ class Window: self.screen.mark_as_dirty() wakeup() - def set_geometry(self, new_geometry): + def set_geometry(self, window_idx, new_geometry): if self.destroyed: return if self.needs_layout or new_geometry.xnum != self.screen.columns or new_geometry.ynum != self.screen.lines: @@ -88,11 +88,12 @@ class Window: self.current_pty_size = ( self.screen.lines, self.screen.columns, max(0, new_geometry.right - new_geometry.left), max(0, new_geometry.bottom - new_geometry.top)) - self.char_grid.resize(new_geometry) + sg = self.char_grid.update_position(new_geometry) self.needs_layout = False boss.resize_pty(self.id) else: - self.char_grid.update_position(new_geometry) + sg = self.char_grid.update_position(new_geometry) + set_window_render_data(self.tab_id, window_idx, self.vao_id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen) self.geometry = new_geometry def contains(self, x, y): @@ -281,6 +282,11 @@ class Window: def buf_toggled(self, is_main_linebuf): self.screen.scroll(SCROLL_FULL, False) + def destroy(self): + if self.vao_id is not None: + remove_vao(self.vao_id) + self.vao_id = None + # actions {{{ def show_scrollback(self):