diff --git a/kitty/boss.py b/kitty/boss.py index 2d379cac0..53a33e76e 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -15,8 +15,8 @@ from .constants import ( from .fast_data_types import ( GLFW_CURSOR, GLFW_CURSOR_HIDDEN, GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT, ChildMonitor, Timers as _Timers, - destroy_sprite_map, glfw_post_empty_event, layout_sprite_map, - resize_gl_viewport + destroy_global_data, destroy_sprite_map, glfw_post_empty_event, + layout_sprite_map, resize_gl_viewport ) from .fonts.render import render_cell_wrapper, set_font_family from .keys import ( @@ -365,7 +365,6 @@ class Boss: self.glfw_window.set_title(self.glfw_window_title) if isosx: cocoa_update_title(self.glfw_window_title) - self.tab_manager.render() for window in tab.visible_windows(): if not window.needs_layout: window.char_grid.render_cells() @@ -408,6 +407,7 @@ class Boss: t.destroy() del self.tab_manager destroy_sprite_map() + destroy_global_data() del self.glfw_window def paste_from_clipboard(self): diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 1713fc370..b5dfbccc6 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -424,6 +424,7 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) { static double last_render_at = -DBL_MAX; draw_borders_func draw_borders = NULL; +draw_cells_func draw_cells = NULL; static inline bool render(ChildMonitor *self, double *timeout) { @@ -432,6 +433,9 @@ render(ChildMonitor *self, double *timeout) { double time_since_last_render = now - last_render_at; if (time_since_last_render > self->repaint_delay) { draw_borders(); +#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 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 b05ad9c92..fb63e4652 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -22,6 +22,7 @@ #define MIN(x, y) (((x) > (y)) ? (y) : (x)) #define xstr(s) str(s) #define str(s) #s +#define fatal(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(EXIT_FAILURE); } typedef uint32_t char_type; typedef uint32_t color_type; @@ -292,8 +293,22 @@ typedef struct { 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 @@ -313,6 +328,8 @@ extern GlobalState global_state; 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(); diff --git a/kitty/shaders.c b/kitty/shaders.c index 2da39ec27..33948ebd0 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -33,8 +33,6 @@ static char glbuf[4096]; #define GL_STACK_OVERFLOW 0x0503 #endif -#define fatal(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(EXIT_FAILURE); } - #ifdef ENABLE_DEBUG_GL static void check_for_gl_error(int line) { @@ -596,7 +594,7 @@ create_cell_vao() { } static void -draw_cells(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen) { +draw_cells_impl(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen) { size_t sz; void *address; bool inverted = screen_invert_colors(screen); @@ -842,7 +840,7 @@ PYWRAP1(draw_cells) { Screen *screen; PA("iffffO", &vao_idx, &xstart, &ystart, &dx, &dy, &screen); - draw_cells(vao_idx, xstart, ystart, dx, dy, screen); + draw_cells_impl(vao_idx, xstart, ystart, dx, dy, screen); Py_RETURN_NONE; } NO_ARG(destroy_sprite_map) @@ -900,6 +898,7 @@ PYWRAP0(check_for_extensions) { #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} static PyMethodDef module_methods[] = { {"glewInit", (PyCFunction)glew_init, METH_NOARGS, NULL}, + {"draw_cells", (PyCFunction)pydraw_cells, METH_VARARGS, NULL}, M(compile_program, METH_VARARGS), MW(check_for_extensions, METH_NOARGS), MW(create_vao, METH_NOARGS), @@ -917,7 +916,6 @@ static PyMethodDef module_methods[] = { MW(send_borders_rects, METH_VARARGS), MW(init_cell_program, METH_NOARGS), MW(create_cell_vao, METH_NOARGS), - MW(draw_cells, METH_VARARGS), MW(layout_sprite_map, METH_VARARGS), MW(destroy_sprite_map, METH_NOARGS), MW(resize_gl_viewport, METH_VARARGS), @@ -965,6 +963,7 @@ init_shaders(PyObject *module) { PyModule_AddObject(module, "GL_VERSION_REQUIRED", Py_BuildValue("II", REQUIRED_VERSION_MAJOR, REQUIRED_VERSION_MINOR)); if (PyModule_AddFunctions(module, module_methods) != 0) return false; draw_borders = &draw_borders_impl; + draw_cells = &draw_cells_impl; return true; } // }}} diff --git a/kitty/state.c b/kitty/state.c index 21a4ccc87..238c03985 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -8,15 +8,55 @@ #define IS_STATE #include "data-types.h" +GlobalState global_state = {{0}}; +static const Tab EMPTY_TAB = {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) { \ + size_t capacity = sizeof(array)/sizeof(array[0]); \ + for (size_t i = 0; i < count; i++) { \ + if (array[i].id == qid) { \ + 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)--; \ + } \ + }} + +static inline void +add_tab(unsigned int id) { + ensure_can_add(global_state.tabs, global_state.num_tabs, "Too many children (add_tab)"); + global_state.tabs[global_state.num_tabs] = EMPTY_TAB; + global_state.tabs[global_state.num_tabs].id = id; + global_state.num_tabs++; +} + +static inline void +remove_tab(unsigned int id) { + REMOVER(global_state.tabs, id, global_state.num_tabs, EMPTY_TAB, Tab); +} + +static inline void +set_active_tab(unsigned int idx) { + global_state.active_tab = idx; +} + +static inline void +swap_tabs(unsigned int a, unsigned int b) { + Tab t = global_state.tabs[b]; + global_state.tabs[b] = global_state.tabs[a]; + global_state.tabs[a] = t; +} + +// Python API {{{ #define PYWRAP0(name) static PyObject* py##name(PyObject UNUSED *self) #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) #define PYWRAP2(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args, PyObject *kw) #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; } -GlobalState global_state = {{0}}; - - -// Python API {{{ 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); } S(visual_bell_duration, PyFloat_AsDouble); @@ -25,10 +65,36 @@ PYWRAP1(set_options) { Py_RETURN_NONE; } +PYWRAP1(set_tab_bar_render_data) { +#define A(name) &(global_state.tab_bar_render_data.name) + Py_CLEAR(global_state.tab_bar_render_data.screen); + PA("iffffO", A(vao_idx), A(xstart), A(ystart), A(dx), A(dy), A(screen)); + Py_INCREF(global_state.tab_bar_render_data.screen); + Py_RETURN_NONE; +#undef A +} + +PYWRAP0(destroy_global_data) { + Py_CLEAR(global_state.tab_bar_render_data.screen); + Py_RETURN_NONE; +} + +ONE_UINT(add_tab) +ONE_UINT(remove_tab) +ONE_UINT(set_active_tab) +TWO_UINT(swap_tabs) + #define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL} #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} + static PyMethodDef module_methods[] = { MW(set_options, METH_O), + MW(add_tab, METH_O), + MW(remove_tab, METH_O), + MW(set_active_tab, METH_O), + MW(swap_tabs, METH_VARARGS), + MW(set_tab_bar_render_data, METH_VARARGS), + MW(destroy_global_data, METH_NOARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/tabs.py b/kitty/tabs.py index 4481f1f28..7d387c3c9 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -4,17 +4,18 @@ from collections import deque, namedtuple from functools import partial +from itertools import count from .borders import Borders -from .char_grid import calculate_gl_geometry, render_cells +from .char_grid import calculate_gl_geometry from .child import Child from .config import build_ansi_color_table from .constants import ( - WindowGeometry, appname, cell_size, get_boss, shell_path, - viewport_size + WindowGeometry, appname, cell_size, get_boss, shell_path, viewport_size ) from .fast_data_types import ( - DECAWM, Screen, create_cell_vao, glfw_post_empty_event + DECAWM, Screen, add_tab, create_cell_vao, glfw_post_empty_event, + remove_tab, set_active_tab, swap_tabs, set_tab_bar_render_data ) from .layout import Rect, all_layouts from .utils import color_as_int @@ -22,16 +23,19 @@ from .window import Window TabbarData = namedtuple('TabbarData', 'title is_active is_last') borders = None +tab_counter = count() +next(tab_counter) def SpecialWindow(cmd, stdin=None, override_title=None): return (cmd, stdin, override_title) -class Tab: +class Tab: # {{{ def __init__(self, opts, args, on_title_change, session_tab=None, special_window=None): global borders + self.id = next(tab_counter) self.opts, self.args = opts, args self.name = getattr(session_tab, 'name', '') self.on_title_change = on_title_change @@ -199,9 +203,10 @@ class Tab: def __repr__(self): return 'Tab(title={}, id={})'.format(self.name or self.title, hex(id(self))) +# }}} -class TabBar: +class TabBar: # {{{ def __init__(self, opts): self.num_tabs = 1 @@ -238,7 +243,8 @@ class TabBar: self.tab_bar_blank_rects = (Rect(0, g.top, g.left, g.bottom), Rect(g.right - 1, g.top, viewport_width, g.bottom)) else: self.tab_bar_blank_rects = () - self.screen_geometry = calculate_gl_geometry(g, viewport_width, viewport_height, cell_width, cell_height) + self.screen_geometry = sg = calculate_gl_geometry(g, viewport_width, viewport_height, cell_width, cell_height) + set_tab_bar_render_data(self.vao_id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen) def update(self, data): if self.layout_changed is None: @@ -270,18 +276,15 @@ class TabBar: self.cell_ranges = cr glfw_post_empty_event() - def render(self): - if self.layout_changed is not None: - render_cells(self.vao_id, self.screen_geometry, self.screen) - def tab_at(self, x): x = (x - self.window_geometry.left) // self.cell_width for i, (a, b) in enumerate(self.cell_ranges): if a <= x <= b: return i +# }}} -class TabManager: +class TabManager: # {{{ def __init__(self, opts, args): self.opts, self.args = opts, args @@ -291,9 +294,22 @@ class TabManager: self.tab_bar.layout(*self.tab_bar_layout_data) self.active_tab_idx = 0 + def _add_tab(self, tab): + add_tab(tab.id) + self.tabs.append(tab) + + def _remove_tab(self, tab): + remove_tab(tab.id) + self.tabs.remove(tab) + + def _set_active_tab(self, idx): + self.active_tab_idx = idx + set_active_tab(idx) + def init(self, startup_session): - self.tabs = [Tab(self.opts, self.args, self.title_changed, t) for t in startup_session.tabs] - self.active_tab_idx = startup_session.active_tab_idx + for t in startup_session.tabs: + self._add_tab(Tab(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: get_boss().tabbar_visibility_changed() self.update_tab_bar() @@ -310,7 +326,7 @@ class TabManager: tab.relayout() def set_active_tab(self, idx): - self.active_tab_idx = idx + self._set_active_tab(idx) self.active_tab.relayout_borders() self.update_tab_bar() @@ -337,7 +353,8 @@ class TabManager: idx = self.active_tab_idx nidx = (idx + len(self.tabs) + delta) % len(self.tabs) self.tabs[idx], self.tabs[nidx] = self.tabs[nidx], self.tabs[idx] - self.active_tab_idx = nidx + swap_tabs(idx, nidx) + self._set_active_tab(nidx) self.update_tab_bar() def title_changed(self, new_title): @@ -345,16 +362,17 @@ class TabManager: def new_tab(self, special_window=None): needs_resize = len(self.tabs) == 1 - self.active_tab_idx = len(self.tabs) - self.tabs.append(Tab(self.opts, self.args, self.title_changed, special_window=special_window)) + idx = len(self.tabs) + self._add_tab(Tab(self.opts, self.args, self.title_changed, special_window=special_window)) + self._set_active_tab(idx) self.update_tab_bar() if needs_resize: get_boss().tabbar_visibility_changed() def remove(self, tab): needs_resize = len(self.tabs) == 2 - self.tabs.remove(tab) - self.active_tab_idx = max(0, min(self.active_tab_idx, len(self.tabs) - 1)) + self._remove_tab(tab) + self._set_active_tab(max(0, min(self.active_tab_idx, len(self.tabs) - 1))) self.update_tab_bar() tab.destroy() if needs_resize: @@ -386,3 +404,4 @@ class TabManager: if len(self.tabs) < 2: return self.tab_bar.render() +# }}}