From d1aa59080fad25aa9ce0111ae41bbd45cf09b248 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 8 Nov 2019 14:22:02 +0530 Subject: [PATCH] Add an API to attach and detach windows from tabs --- kitty/state.c | 90 +++++++++++++++++++++++++++++++++++---------------- kitty/tabs.py | 45 ++++++++++++++++++++++---- 2 files changed, 102 insertions(+), 33 deletions(-) diff --git a/kitty/state.c b/kitty/state.c index 1c2dd32fe..5a404f882 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -116,31 +116,6 @@ add_window(id_type os_window_id, id_type tab_id, PyObject *title) { return 0; } -static inline id_type -change_window_tab(id_type window_id, id_type old_tab_os_window_id, id_type old_tab_id, id_type new_tab_os_window_id, id_type new_tab_id) { - // move the window description from one tab to another - Window* new_window_description = 0; - WITH_TAB(new_tab_os_window_id, new_tab_id); - ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true); - make_os_window_context_current(osw); - zero_at_i(tab->windows, tab->num_windows); - new_window_description = &tab->windows[tab->num_windows]; - ++tab->num_windows; - END_WITH_TAB; - if (new_window_description == 0) return 0; // could not find the window inside the old tab - WITH_TAB(old_tab_os_window_id, old_tab_id); - for (size_t i = 0; i < tab->num_windows; i++) { - if (tab->windows[i].id == window_id) { - memcpy(new_window_description, &tab->windows[i], sizeof(Window)); - zero_at_i(tab->windows, i); - remove_i_from_array(tab->windows, i, tab->num_windows); - break; - } - } - END_WITH_TAB; - return new_window_description->id; -} - static inline void update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyObject *title) { WITH_TAB(os_window_id, tab_id); @@ -174,6 +149,52 @@ remove_window(id_type os_window_id, id_type tab_id, id_type id) { END_WITH_TAB; } +static inline void +no_op_on_window(Window *w UNUSED) {} + +typedef struct { + unsigned int num_windows, capacity; + Window *windows; +} DetachedWindows; + +static DetachedWindows detached_windows = {0}; + + +static void +add_detached_window(Window *w) { + ensure_space_for(&detached_windows, windows, Window, detached_windows.num_windows + 1, capacity, 8, true); + memcpy(detached_windows.windows + detached_windows.num_windows++, w, sizeof(Window)); +} + +static inline void +detach_window(id_type os_window_id, id_type tab_id, id_type id) { + WITH_TAB(os_window_id, tab_id); + for (size_t i = 0; i < tab->num_windows; i++) { + if (tab->windows[i].id == id) { + add_detached_window(tab->windows + i); + zero_at_i(tab->windows, i); + remove_i_from_array(tab->windows, i, tab->num_windows); + } + } + END_WITH_TAB; +} + + +static inline void +attach_window(id_type os_window_id, id_type tab_id, id_type id) { + WITH_TAB(os_window_id, tab_id); + for (size_t i = 0; i < detached_windows.num_windows; i++) { + if (detached_windows.windows[i].id == id) { + ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true); + memcpy(tab->windows + tab->num_windows++, detached_windows.windows + i, sizeof(Window)); + zero_at_i(detached_windows.windows, i); + remove_i_from_array(detached_windows.windows, i, detached_windows.num_windows); + break; + } + } + END_WITH_TAB; +} + static inline void destroy_tab(Tab *tab) { for (size_t i = tab->num_windows; i > 0; i--) remove_window_inner(tab, tab->windows[i - 1].id); @@ -778,10 +799,11 @@ PYWRAP0(destroy_global_data) { THREE_ID_OBJ(update_window_title) THREE_ID(remove_window) +THREE_ID(detach_window) +THREE_ID(attach_window) PYWRAP1(resolve_key_mods) { int mods; PA("ii", &kitty_mod, &mods); return PyLong_FromLong(resolve_mods(mods)); } 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)); } -PYWRAP1(change_window_tab) { id_type a, b, c, d, e; PA("KKKKK", &a, &b, &c, &d, &e); return PyLong_FromUnsignedLongLong(change_window_tab(a, b, c, d, e)); } 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) @@ -803,10 +825,11 @@ static PyMethodDef module_methods[] = { MW(pt_to_px, METH_VARARGS), MW(add_tab, METH_O), MW(add_window, METH_VARARGS), - MW(change_window_tab, METH_VARARGS), MW(update_window_title, METH_VARARGS), MW(remove_tab, METH_VARARGS), MW(remove_window, METH_VARARGS), + MW(detach_window, METH_VARARGS), + MW(attach_window, METH_VARARGS), MW(set_active_tab, METH_VARARGS), MW(set_active_window, METH_VARARGS), MW(swap_tabs, METH_VARARGS), @@ -832,6 +855,15 @@ static PyMethodDef module_methods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; +static void +finalize(void) { + while(detached_windows.num_windows--) { + destroy_window(&detached_windows.windows[detached_windows.num_windows]); + } + if (detached_windows.windows) free(detached_windows.windows); + detached_windows.capacity = 0; +} + bool init_state(PyObject *module) { global_state.font_sz_in_pts = 11.0; @@ -845,6 +877,10 @@ init_state(PyObject *module) { if (PyStructSequence_InitType2(&RegionType, ®ion_desc) != 0) return false; Py_INCREF((PyObject *) &RegionType); PyModule_AddObject(module, "Region", (PyObject *) &RegionType); + if (Py_AtExit(finalize) != 0) { + PyErr_SetString(PyExc_RuntimeError, "Failed to register the state at exit handler"); + return false; + } return true; } // }}} diff --git a/kitty/tabs.py b/kitty/tabs.py index 65bbcc8fc..dc7c13847 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -4,14 +4,14 @@ import weakref from collections import deque, namedtuple -from functools import partial from contextlib import suppress +from functools import partial from .borders import Borders from .child import Child from .constants import appname, get_boss, is_macos, is_wayland from .fast_data_types import ( - add_tab, mark_tab_bar_dirty, next_window_id, + add_tab, attach_window, detach_window, mark_tab_bar_dirty, next_window_id, pt_to_px, remove_tab, remove_window, ring_bell, set_active_tab, swap_tabs, x11_window_id ) @@ -232,6 +232,10 @@ class Tab: # {{{ ans.fork() return ans + def _add_window(self, window, location=None): + self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx, location) + self.relayout_borders() + def new_window( self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None, env=None, location=None, @@ -245,8 +249,7 @@ class Tab: # {{{ overlaid.overlay_window_id = window.id # Must add child before laying out so that resize_pty succeeds get_boss().add_child(window) - self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx, location) - self.relayout_borders() + self._add_window(window, location=location) return window def new_special_window(self, special_window, location=None, copy_colors_from=None): @@ -265,13 +268,16 @@ class Tab: # {{{ if w.id == old_window_id: return idx - def remove_window(self, window): + def remove_window(self, window, destroy=True): idx = self.previous_active_window_idx(1) next_window_id = None if idx is not None: next_window_id = self.windows[idx].id active_window_idx = self.current_layout.remove_window(self.windows, window, self.active_window_idx) - remove_window(self.os_window_id, self.id, window.id) + if destroy: + remove_window(self.os_window_id, self.id, window.id) + else: + detach_window(self.os_window_id, self.id, window.id) if window.overlay_for is not None: for idx, q in enumerate(self.windows): if q.id == window.overlay_for: @@ -294,6 +300,33 @@ class Tab: # {{{ if active_window: self.title_changed(active_window) + def detach_window(self, window): + underlaid_window = None + overlaid_window = window + if window.overlay_for: + for x in self.windows: + if x.id == window.overlay_for: + underlaid_window = x + break + elif window.overlay_window_id: + underlaid_window = window + overlaid_window = None + for x in self.windows: + if x.id == window.overlay_window_id: + overlaid_window = x + break + if overlaid_window is not None: + self.remove_window(overlaid_window, destroy=False) + if underlaid_window is not None: + self.remove_window(underlaid_window, destroy=False) + return underlaid_window, overlaid_window + + def attach_window(self, window): + window.tab_id = self.id + window.os_window_id = self.os_window_id + attach_window(self.os_window_id, self.id, window.id) + self._add_window(window) + def set_active_window_idx(self, idx): if idx != self.active_window_idx: self.active_window_idx = self.current_layout.set_active_window(self.windows, idx)