Compare commits

...

9 Commits
f2 ... float

Author SHA1 Message Date
Kovid Goyal
6d826c37b9 More work on floats 2025-04-19 08:15:53 +05:30
Kovid Goyal
e3fa7fc60e Wire up drawing code for floats 2025-04-18 09:21:17 +05:30
Kovid Goyal
77d2159d3f Ensure all windows in OS Window are destroyed when OS Window is closed 2025-04-16 17:21:58 +05:30
Kovid Goyal
da753c3f5a DRYer 2025-04-16 17:11:15 +05:30
Kovid Goyal
d8889a2f08 Serialize floats 2025-04-16 17:06:06 +05:30
Kovid Goyal
14ff29426d Track child relationships directly in python for performance 2025-04-16 16:46:18 +05:30
Kovid Goyal
a93a6af025 Allow creating float-in-float 2025-04-16 16:18:38 +05:30
Kovid Goyal
d5398f84d8 Function to add a floating window 2025-04-16 16:18:38 +05:30
Kovid Goyal
08d90db4c4 Start work on floating windows 2025-04-16 16:18:38 +05:30
9 changed files with 290 additions and 104 deletions

View File

@@ -93,6 +93,7 @@ from .fast_data_types import (
os_window_focus_counters, os_window_focus_counters,
os_window_font_size, os_window_font_size,
redirect_mouse_handling, redirect_mouse_handling,
remove_floating_window,
ring_bell, ring_bell,
run_with_activation_token, run_with_activation_token,
safe_pipe, safe_pipe,
@@ -360,6 +361,7 @@ class Boss:
self.clipboard = Clipboard() self.clipboard = Clipboard()
self.window_for_dispatch: Window | None = None self.window_for_dispatch: Window | None = None
self.primary_selection = Clipboard(ClipboardType.primary_selection) self.primary_selection = Clipboard(ClipboardType.primary_selection)
self.window_floats_map: dict[int, list[int]] = {}
self.update_check_started = False self.update_check_started = False
self.peer_data_map: dict[int, dict[str, Sequence[str]] | None] = {} self.peer_data_map: dict[int, dict[str, Sequence[str]] | None] = {}
self.background_process_death_notify_map: dict[int, Callable[[int, Exception | None], None]] = {} self.background_process_death_notify_map: dict[int, Callable[[int, Exception | None], None]] = {}
@@ -907,6 +909,11 @@ class Boss:
for w, val in changes.items(): for w, val in changes.items():
w.ignore_focus_changes = val w.ignore_focus_changes = val
def descendant_window_ids(self, parent_id: int) -> Iterator[int]:
for cid in self.window_floats_map.get(parent_id, ()):
yield cid
yield from self.descendant_window_ids(cid)
def on_child_death(self, window_id: int) -> None: def on_child_death(self, window_id: int) -> None:
prev_active_window = self.active_window prev_active_window = self.active_window
window = self.window_id_map.pop(window_id, None) window = self.window_id_map.pop(window_id, None)
@@ -921,16 +928,25 @@ class Boss:
traceback.print_exc() traceback.print_exc()
os_window_id = window.os_window_id os_window_id = window.os_window_id
window.destroy() window.destroy()
tm = self.os_window_map.get(os_window_id) for cid in tuple(self.descendant_window_ids(window.id)):
tab = None self.window_floats_map.pop(cid, None)
if tm is not None: cw = self.window_id_map.pop(cid, None)
for q in tm: if cw is not None:
if window in q: self.child_monitor.mark_for_close(cw.id)
tab = q self.window_floats_map.pop(window.id, None)
break if window.floating_in:
if tab is not None: remove_floating_window(window.os_window_id, window.tab_id, window.floating_in, window.id)
tab.remove_window(window) else:
self._cleanup_tab_after_window_removal(tab) tm = self.os_window_map.get(os_window_id)
tab = None
if tm is not None:
for q in tm:
if window in q:
tab = q
break
if tab is not None:
tab.remove_window(window)
self._cleanup_tab_after_window_removal(tab)
for removal_action in window.actions_on_removal: for removal_action in window.actions_on_removal:
try: try:
removal_action(window) removal_action(window)
@@ -1801,11 +1817,15 @@ class Boss:
def on_os_window_closed(self, os_window_id: int, viewport_width: int, viewport_height: int) -> None: def on_os_window_closed(self, os_window_id: int, viewport_width: int, viewport_height: int) -> None:
self.cached_values['window-size'] = viewport_width, viewport_height self.cached_values['window-size'] = viewport_width, viewport_height
windows_in_os_window = {w.id: w for w in self.window_id_map.values() if w.os_window_id == os_window_id}
tm = self.os_window_map.pop(os_window_id, None) tm = self.os_window_map.pop(os_window_id, None)
if tm is not None: if tm is not None:
tm.destroy() tm.destroy() # this will call destroy on all windows
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): for window_id, w in windows_in_os_window.items():
self.child_monitor.mark_for_close(window_id)
self.window_id_map.pop(window_id, None) self.window_id_map.pop(window_id, None)
self.window_floats_map.pop(window_id, None)
w.destroy() # in case tm was None
if not self.os_window_map and is_macos: if not self.os_window_map and is_macos:
cocoa_set_menubar_title('') cocoa_set_menubar_title('')
action = self.os_window_death_actions.pop(os_window_id, None) action = self.os_window_death_actions.pop(os_window_id, None)

View File

@@ -565,7 +565,6 @@ mark_child_for_close(ChildMonitor *self, id_type window_id) {
return found; return found;
} }
static PyObject * static PyObject *
mark_for_close(ChildMonitor *self, PyObject *args) { mark_for_close(ChildMonitor *self, PyObject *args) {
#define mark_for_close_doc "Mark a child to be removed from the child monitor" #define mark_for_close_doc "Mark a child to be removed from the child monitor"
@@ -705,8 +704,63 @@ change_menubar_title(PyObject *title UNUSED) {
#endif #endif
} }
#define WD w->render_data
static bool static bool
prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg, bool scan_for_animated_images) { prepare_to_render_window(OSWindow *os_window, Tab *tab, Window *w, bool is_active_non_floating_window, monotonic_t now, bool scan_for_animated_images, id_type *active_window_id) {
bool needs_render = false;
if (w->last_drag_scroll_at > 0) {
if (now - w->last_drag_scroll_at >= ms_to_monotonic_t(20ll)) {
if (drag_scroll(w, os_window)) {
w->last_drag_scroll_at = now;
set_maximum_wait(ms_to_monotonic_t(20ll));
needs_render = true;
} else w->last_drag_scroll_at = 0;
} else set_maximum_wait(now - w->last_drag_scroll_at);
}
const bool is_floating = w->floating.is_floating;
const bool is_active = is_active_non_floating_window && ((is_floating && w->floating.is_key) || (!is_floating && !w->floating.child_count));
if (is_active) {
*active_window_id = w->id;
if (collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window)) needs_render = true;
WD.screen->cursor_render_info.is_focused = os_window->is_focused;
set_os_window_title_from_window(w, os_window);
if (OPT(cursor_trail)) {
if (update_cursor_trail(&tab->cursor_trail, w, now, os_window)) {
needs_render = true;
// A max wait of zero causes key input processing to be
// slow so handle the case of OPT(repaint_delay) == 0, see https://github.com/kovidgoyal/kitty/pull/8066
set_maximum_wait(MAX(OPT(repaint_delay), ms_to_monotonic_t(1ll)));
} else if (OPT(cursor_trail) > now - WD.screen->cursor->position_changed_by_client_at) {
// If update_cursor_trail failed due to time threshold, the trail animation
// should be evaluated again shortly. Schedule next update when enough time
// has passed since the cursor was last moved.
set_maximum_wait(OPT(cursor_trail) - now + WD.screen->cursor->position_changed_by_client_at);
}
}
} else {
if (WD.screen->cursor_render_info.render_even_when_unfocused) {
if (collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window)) needs_render = true;
WD.screen->cursor_render_info.is_focused = false;
} else {
WD.screen->cursor_render_info.opacity = 0;
}
}
if (scan_for_animated_images) {
monotonic_t min_gap;
if (scan_active_animations(WD.screen->grman, now, &min_gap, true)) needs_render = true;
if (min_gap < MONOTONIC_T_MAX) {
global_state.check_for_active_animated_images = true;
set_maximum_wait(min_gap);
}
}
if (send_cell_data_to_gpu(WD.vao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window)) needs_render = true;
if (WD.screen->start_visual_bell_at != 0) needs_render = true;
return needs_render;
}
static bool
prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, id_type *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg, bool scan_for_animated_images) {
#define TD os_window->tab_bar_render_data #define TD os_window->tab_bar_render_data
bool needs_render = os_window->needs_render; bool needs_render = os_window->needs_render;
os_window->needs_render = false; os_window->needs_render = false;
@@ -728,61 +782,19 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *
color_type first_window_bg = 0; color_type first_window_bg = 0;
for (unsigned int i = 0; i < tab->num_windows; i++) { for (unsigned int i = 0; i < tab->num_windows; i++) {
Window *w = tab->windows + i; Window *w = tab->windows + i;
#define WD w->render_data
if (w->visible && WD.screen) { if (w->visible && WD.screen) {
screen_check_pause_rendering(WD.screen, now); screen_check_pause_rendering(WD.screen, now);
*num_visible_windows += 1; *num_visible_windows += 1;
color_type window_bg = colorprofile_to_color(WD.screen->color_profile, WD.screen->color_profile->overridden.default_bg, WD.screen->color_profile->configured.default_bg).rgb; color_type window_bg = colorprofile_to_color(WD.screen->color_profile, WD.screen->color_profile->overridden.default_bg, WD.screen->color_profile->configured.default_bg).rgb;
const bool is_active_non_floating_window = i == tab->active_window;
if (*num_visible_windows == 1) first_window_bg = window_bg; if (*num_visible_windows == 1) first_window_bg = window_bg;
if (first_window_bg != window_bg) *all_windows_have_same_bg = false; if (first_window_bg != window_bg) *all_windows_have_same_bg = false;
if (w->last_drag_scroll_at > 0) { if (is_active_non_floating_window) *active_window_bg = window_bg;
if (now - w->last_drag_scroll_at >= ms_to_monotonic_t(20ll)) { needs_render |= prepare_to_render_window(os_window, tab, w, is_active_non_floating_window, now, scan_for_animated_images, active_window_id);
if (drag_scroll(w, os_window)) { for (size_t i = 0; i < w->floating.child_count; i++) {
w->last_drag_scroll_at = now; Window *cw = w->floating.children + i;
set_maximum_wait(ms_to_monotonic_t(20ll)); if (cw->render_data.screen) needs_render |= prepare_to_render_window(os_window, tab, cw, is_active_non_floating_window, now, scan_for_animated_images, active_window_id);
needs_render = true;
} else w->last_drag_scroll_at = 0;
} else set_maximum_wait(now - w->last_drag_scroll_at);
} }
bool is_active_window = i == tab->active_window;
if (is_active_window) {
*active_window_id = w->id;
if (collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window)) needs_render = true;
WD.screen->cursor_render_info.is_focused = os_window->is_focused;
set_os_window_title_from_window(w, os_window);
*active_window_bg = window_bg;
if (OPT(cursor_trail)) {
if (update_cursor_trail(&tab->cursor_trail, w, now, os_window)) {
needs_render = true;
// A max wait of zero causes key input processing to be
// slow so handle the case of OPT(repaint_delay) == 0, see https://github.com/kovidgoyal/kitty/pull/8066
set_maximum_wait(MAX(OPT(repaint_delay), ms_to_monotonic_t(1ll)));
} else if (OPT(cursor_trail) > now - WD.screen->cursor->position_changed_by_client_at) {
// If update_cursor_trail failed due to time threshold, the trail animation
// should be evaluated again shortly. Schedule next update when enough time
// has passed since the cursor was last moved.
set_maximum_wait(OPT(cursor_trail) - now + WD.screen->cursor->position_changed_by_client_at);
}
}
} else {
if (WD.screen->cursor_render_info.render_even_when_unfocused) {
if (collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window)) needs_render = true;
WD.screen->cursor_render_info.is_focused = false;
} else {
WD.screen->cursor_render_info.opacity = 0;
}
}
if (scan_for_animated_images) {
monotonic_t min_gap;
if (scan_active_animations(WD.screen->grman, now, &min_gap, true)) needs_render = true;
if (min_gap < MONOTONIC_T_MAX) {
global_state.check_for_active_animated_images = true;
set_maximum_wait(min_gap);
}
}
if (send_cell_data_to_gpu(WD.vao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window)) needs_render = true;
if (WD.screen->start_visual_bell_at != 0) needs_render = true;
} }
} }
return needs_render; return needs_render;
@@ -802,6 +814,16 @@ draw_resizing_text(OSWindow *w) {
} }
} }
static bool
draw_window(OSWindow *os_window, Window *w, bool is_active_non_floating_window, unsigned num_of_visible_windows) {
const bool is_floating = w->floating.is_floating, is_active_window = (
is_active_non_floating_window && ((!is_floating && !w->floating.child_count) || (is_floating && w->floating.is_key)));
draw_cells(WD.vao_idx, &WD, os_window, is_active_window, false, num_of_visible_windows == 1, w);
if (WD.screen->start_visual_bell_at != 0) set_maximum_wait(ANIMATION_SAMPLE_WAIT);
w->cursor_opacity_at_last_render = WD.screen->cursor_render_info.opacity; w->last_cursor_shape = WD.screen->cursor_render_info.shape;
return is_active_window;
}
static void static void
render_prepared_os_window(OSWindow *os_window, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg) { render_prepared_os_window(OSWindow *os_window, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg) {
// ensure all pixels are cleared to background color at least once in every buffer // ensure all pixels are cleared to background color at least once in every buffer
@@ -811,17 +833,17 @@ render_prepared_os_window(OSWindow *os_window, unsigned int active_window_id, co
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window); draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window);
br->is_dirty = false; br->is_dirty = false;
if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) draw_cells(TD.vao_idx, &TD, os_window, true, true, false, NULL); if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) draw_cells(TD.vao_idx, &TD, os_window, true, true, false, NULL);
unsigned int num_of_visible_windows = 0; unsigned num_of_visible_windows = 0;
Window *active_window = NULL; Window *active_window = NULL;
for (unsigned int i = 0; i < tab->num_windows; i++) { if (tab->windows[i].visible) num_of_visible_windows++; } for (unsigned int i = 0; i < tab->num_windows; i++) { if (tab->windows[i].visible) num_of_visible_windows++; }
for (unsigned int i = 0; i < tab->num_windows; i++) { for (unsigned int i = 0; i < tab->num_windows; i++) {
Window *w = tab->windows + i; Window *w = tab->windows + i;
if (w->visible && WD.screen) { if (w->visible && WD.screen) {
bool is_active_window = i == tab->active_window; if (draw_window(os_window, w, i == tab->active_window, num_of_visible_windows)) active_window = w;
if (is_active_window) active_window = w; for (size_t f = 0; f < w->floating.child_count; f++) {
draw_cells(WD.vao_idx, &WD, os_window, is_active_window, false, num_of_visible_windows == 1, w); Window *cw = w->floating.children + f;
if (WD.screen->start_visual_bell_at != 0) set_maximum_wait(ANIMATION_SAMPLE_WAIT); if (cw->render_data.screen && draw_window(os_window, cw, i == tab->active_window, num_of_visible_windows)) active_window = cw;
w->cursor_opacity_at_last_render = WD.screen->cursor_render_info.opacity; w->last_cursor_shape = WD.screen->cursor_render_info.shape; }
} }
} }
if (OPT(cursor_trail) && tab->cursor_trail.needs_render) draw_cursor_trail(&tab->cursor_trail, active_window); if (OPT(cursor_trail) && tab->cursor_trail.needs_render) draw_cursor_trail(&tab->cursor_trail, active_window);
@@ -874,7 +896,7 @@ render_os_window(OSWindow *w, monotonic_t now, bool ignore_render_frames, bool s
w->viewport_size_dirty = false; w->viewport_size_dirty = false;
needs_render = true; needs_render = true;
} }
unsigned int active_window_id = 0, num_visible_windows = 0; id_type active_window_id = 0; unsigned num_visible_windows = 0;
bool all_windows_have_same_bg; bool all_windows_have_same_bg;
color_type active_window_bg = 0; color_type active_window_bg = 0;
if (!w->fonts_data) { log_error("No fonts data found for window id: %llu", w->id); return false; } if (!w->fonts_data) { log_error("No fonts data found for window id: %llu", w->id); return false; }
@@ -1102,22 +1124,18 @@ process_pending_resizes(monotonic_t now) {
} }
static void static void
close_os_window(ChildMonitor *self, OSWindow *os_window) { close_os_window(OSWindow *os_window) {
int w = os_window->window_width, h = os_window->window_height; int w = os_window->window_width, h = os_window->window_height;
if (os_window->before_fullscreen.is_set && is_os_window_fullscreen(os_window)) { if (os_window->before_fullscreen.is_set && is_os_window_fullscreen(os_window)) {
w = os_window->before_fullscreen.w; h = os_window->before_fullscreen.h; w = os_window->before_fullscreen.w; h = os_window->before_fullscreen.h;
} }
destroy_os_window(os_window); destroy_os_window(os_window);
call_boss(on_os_window_closed, "Kii", os_window->id, w, h); call_boss(on_os_window_closed, "Kii", os_window->id, w, h);
for (size_t t=0; t < os_window->num_tabs; t++) {
Tab *tab = os_window->tabs + t;
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
}
remove_os_window(os_window->id); remove_os_window(os_window->id);
} }
static bool static bool
process_pending_closes(ChildMonitor *self) { process_pending_closes(void) {
if (global_state.quit_request == CONFIRMABLE_CLOSE_REQUESTED) { if (global_state.quit_request == CONFIRMABLE_CLOSE_REQUESTED) {
call_boss(quit, ""); call_boss(quit, "");
} }
@@ -1135,14 +1153,14 @@ process_pending_closes(ChildMonitor *self) {
os_window->close_request = CLOSE_BEING_CONFIRMED; os_window->close_request = CLOSE_BEING_CONFIRMED;
call_boss(confirm_os_window_close, "K", os_window->id); call_boss(confirm_os_window_close, "K", os_window->id);
if (os_window->close_request == IMPERATIVE_CLOSE_REQUESTED) { if (os_window->close_request == IMPERATIVE_CLOSE_REQUESTED) {
close_os_window(self, os_window); close_os_window(os_window);
} else has_open_windows = true; } else has_open_windows = true;
break; break;
case CLOSE_BEING_CONFIRMED: case CLOSE_BEING_CONFIRMED:
has_open_windows = true; has_open_windows = true;
break; break;
case IMPERATIVE_CLOSE_REQUESTED: case IMPERATIVE_CLOSE_REQUESTED:
close_os_window(self, os_window); close_os_window(os_window);
break; break;
} }
} }
@@ -1297,7 +1315,7 @@ process_global_state(void *data) {
#endif #endif
report_reaped_pids(); report_reaped_pids();
bool should_quit = false; bool should_quit = false;
if (global_state.has_pending_closes) should_quit = process_pending_closes(self); if (global_state.has_pending_closes) should_quit = process_pending_closes();
if (should_quit) { if (should_quit) {
stop_main_loop(); stop_main_loop();
} else { } else {

View File

@@ -1740,6 +1740,8 @@ def render_decoration(
which: DecorationTypes, cell_width: int, cell_height: int, underline_position: int, underline_thickness: int, dpi: float = 96.0 which: DecorationTypes, cell_width: int, cell_height: int, underline_position: int, underline_thickness: int, dpi: float = 96.0
) -> bytes: ... ) -> bytes: ...
def os_window_is_invisible(os_window_id: int) -> bool: ... def os_window_is_invisible(os_window_id: int) -> bool: ...
def remove_floating_window(os_window_id: int, tab_id: int, non_floating_window_id: int, window_id: int) -> None: ...
def add_floating_window(os_window_id: int, tab_id: int, floating_in: int) -> tuple[int, int]: ...
class MousePosition(TypedDict): class MousePosition(TypedDict):
cell_x: int cell_x: int

10
kitty/floats.py Normal file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2025, Kovid Goyal <kovid at kovidgoyal.net>
from .types import WindowGeometry
from .typing import WindowType
def calculate_geometry_for_float(child: WindowType, parent_geometry: WindowGeometry) -> WindowGeometry:
# TODO: Implement this
return parent_geometry

View File

@@ -81,7 +81,7 @@ of the active window in the tab is used as the tab title. The special value
--type --type
type=choices type=choices
default=window default=window
choices=window,tab,os-window,overlay,overlay-main,background,clipboard,primary choices=window,tab,os-window,overlay,overlay-main,background,clipboard,primary,float
Where to launch the child process: Where to launch the child process:
:code:`window` :code:`window`
@@ -105,6 +105,9 @@ Where to launch the child process:
directory, the input text for kittens, launch commands, etc. Useful if this overlay is directory, the input text for kittens, launch commands, etc. Useful if this overlay is
intended to run for a long time as a primary window. intended to run for a long time as a primary window.
:code:`float`
A floating window displayed on top of the parent window.
:code:`background` :code:`background`
The process will be run in the :italic:`background`, without a kitty The process will be run in the :italic:`background`, without a kitty
window. Note that if :option:`kitten @ launch --allow-remote-control` is window. Note that if :option:`kitten @ launch --allow-remote-control` is
@@ -122,7 +125,8 @@ Where to launch the child process:
--keep-focus --dont-take-focus --keep-focus --dont-take-focus
type=bool-set type=bool-set
Keep the focus on the currently active window instead of switching to the newly Keep the focus on the currently active window instead of switching to the newly
opened window. opened window. Note that floating windows always take focus from their parent
non-floating window.
--cwd --cwd
@@ -722,8 +726,8 @@ def _launch(
tab = tab_for_window(boss, opts, target_tab, next_to) tab = tab_for_window(boss, opts, target_tab, next_to)
watchers = load_watch_modules(opts.watcher) watchers = load_watch_modules(opts.watcher)
with Window.set_ignore_focus_changes_for_new_windows(opts.keep_focus): with Window.set_ignore_focus_changes_for_new_windows(opts.keep_focus):
new_window: Window = tab.new_window( new_window = tab.new_window(
env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, next_to=next_to, **kw) env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, next_to=next_to, is_float=opts.type == 'float', **kw)
if spacing: if spacing:
patch_window_edges(new_window, spacing) patch_window_edges(new_window, spacing)
tab.relayout() tab.relayout()

View File

@@ -12,7 +12,7 @@
GlobalState global_state = {{0}}; GlobalState global_state = {{0}};
#define REMOVER(array, qid, count, destroy, capacity) { \ #define REMOVER(array, qid, count, destroy) { \
for (size_t i = 0; i < count; i++) { \ for (size_t i = 0; i < count; i++) { \
if (array[i].id == qid) { \ if (array[i].id == qid) { \
destroy(array + i); \ destroy(array + i); \
@@ -315,9 +315,31 @@ add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
return 0; return 0;
} }
static Window*
add_floating_window(id_type os_window_id, id_type tab_id, id_type floating_in) {
WITH_TAB(os_window_id, tab_id);
for (size_t i = 0; i < tab->num_windows; i++) {
Window *window = tab->windows + i;
if (window->id != floating_in) window = find_child_window(window, floating_in);
if (window) {
make_os_window_context_current(osw);
ensure_space_for(&window->floating, children, Window, window->floating.child_count + 1, child_capacity, 1, true);
zero_at_i(window->floating.children, window->floating.child_count);
initialize_window(window->floating.children + window->floating.child_count, NULL, true);
Window *ans = &window->floating.children[window->floating.child_count++];
ans->floating.is_floating = true;
ans->floating.parent = floating_in;
ans->floating.non_floating_ancestor = tab->windows[i].id;
return ans;
}
}
END_WITH_TAB;
return NULL;
}
static void static void
update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyObject *title) { update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyObject *title) {
WITH_WINDOW(os_window_id, tab_id, window_id) WITH_WINDOW(os_window_id, tab_id, window_id);
Py_CLEAR(window->title); Py_CLEAR(window->title);
window->title = title; window->title = title;
Py_XINCREF(window->title); Py_XINCREF(window->title);
@@ -360,13 +382,15 @@ destroy_window(Window *w) {
decref_window_logo(global_state.all_window_logos, w->window_logo.id); decref_window_logo(global_state.all_window_logos, w->window_logo.id);
w->window_logo.id = 0; w->window_logo.id = 0;
} }
for (size_t i = 0; i < w->floating.child_count; i++) destroy_window(&w->floating.children[i]);
free(w->floating.children); w->floating.children = NULL;
} }
static void static void
remove_window_inner(Tab *tab, id_type id) { remove_window_inner(Tab *tab, id_type id) {
id_type active_window_id = 0; id_type active_window_id = 0;
if (tab->active_window < tab->num_windows) active_window_id = tab->windows[tab->active_window].id; if (tab->active_window < tab->num_windows) active_window_id = tab->windows[tab->active_window].id;
REMOVER(tab->windows, id, tab->num_windows, destroy_window, tab->capacity); REMOVER(tab->windows, id, tab->num_windows, destroy_window);
if (active_window_id) { if (active_window_id) {
for (unsigned int w = 0; w < tab->num_windows; w++) { for (unsigned int w = 0; w < tab->num_windows; w++) {
if (tab->windows[w].id == active_window_id) { if (tab->windows[w].id == active_window_id) {
@@ -376,6 +400,30 @@ remove_window_inner(Tab *tab, id_type id) {
} }
} }
static bool
remove_floating_window_inner(Window *window, id_type id) {
Window *w;
for (size_t i = 0; i < window->floating.child_count; i++) {
if ((w = window->floating.children + i)->id == id) {
destroy_window(w);
zero_at_i(window->floating.children, i);
remove_i_from_array(window->floating.children, i, window->floating.child_count);
return true;
}
if (remove_floating_window_inner(window->floating.children + i, id)) return true;
}
return false;
}
static void
remove_floating_window(id_type os_window_id, id_type tab_id, id_type parent_non_floating_window, id_type id) {
WITH_WINDOW(os_window_id, tab_id, parent_non_floating_window);
make_os_window_context_current(osw);
remove_floating_window_inner(window, id);
END_WITH_WINDOW;
}
static void static void
remove_window(id_type os_window_id, id_type tab_id, id_type id) { remove_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id); WITH_TAB(os_window_id, tab_id);
@@ -462,7 +510,7 @@ remove_tab_inner(OSWindow *os_window, id_type id) {
id_type active_tab_id = 0; id_type active_tab_id = 0;
if (os_window->active_tab < os_window->num_tabs) active_tab_id = os_window->tabs[os_window->active_tab].id; if (os_window->active_tab < os_window->num_tabs) active_tab_id = os_window->tabs[os_window->active_tab].id;
make_os_window_context_current(os_window); make_os_window_context_current(os_window);
REMOVER(os_window->tabs, id, os_window->num_tabs, destroy_tab, os_window->capacity); REMOVER(os_window->tabs, id, os_window->num_tabs, destroy_tab);
if (active_tab_id) { if (active_tab_id) {
for (unsigned int i = 0; i < os_window->num_tabs; i++) { for (unsigned int i = 0; i < os_window->num_tabs; i++) {
if (os_window->tabs[i].id == active_tab_id) { if (os_window->tabs[i].id == active_tab_id) {
@@ -501,7 +549,7 @@ remove_os_window(id_type os_window_id) {
END_WITH_OS_WINDOW END_WITH_OS_WINDOW
if (found) { if (found) {
WITH_OS_WINDOW_REFS WITH_OS_WINDOW_REFS
REMOVER(global_state.os_windows, os_window_id, global_state.num_os_windows, destroy_os_window_item, global_state.capacity); REMOVER(global_state.os_windows, os_window_id, global_state.num_os_windows, destroy_os_window_item);
END_WITH_OS_WINDOW_REFS END_WITH_OS_WINDOW_REFS
update_os_window_references(); update_os_window_references();
} }
@@ -643,6 +691,26 @@ make_window_context_current(id_type window_id) {
return false; return false;
} }
void
iter_child_windows(Window *self, bool(*callback)(Window *, void *data), void *data) {
for (size_t i = 0; i < self->floating.child_count; i++) {
Window *w = self->floating.children + i;
if (!callback(w, data)) return;
iter_child_windows(w, callback, data);
}
}
Window*
find_child_window(Window *self, id_type child_id) {
for (size_t i = 0; i < self->floating.child_count; i++) {
Window *w = self->floating.children + i;
if (w->id == child_id) return w;
if ((w = find_child_window(w, child_id))) return w;
}
return NULL;
}
void void
dispatch_pending_clicks(id_type timer_id UNUSED, void *data UNUSED) { dispatch_pending_clicks(id_type timer_id UNUSED, void *data UNUSED) {
bool dispatched = false; bool dispatched = false;
@@ -675,7 +743,8 @@ update_ime_position_for_window(id_type window_id, bool force, int update_focus)
Tab *qtab = osw->tabs + t; Tab *qtab = osw->tabs + t;
for (size_t w = 0; w < qtab->num_windows; w++) { for (size_t w = 0; w < qtab->num_windows; w++) {
Window *window = qtab->windows + w; Window *window = qtab->windows + w;
if (window->id == window_id) { if (window_id != window->id) window = find_child_window(window, window_id);
if (window != NULL) {
// The screen may not be ready after the new window is created and focused, and still needs to enable IME. // The screen may not be ready after the new window is created and focused, and still needs to enable IME.
if ((window->render_data.screen && (force || osw->is_focused)) || update_focus > 0) { if ((window->render_data.screen && (force || osw->is_focused)) || update_focus > 0) {
OSWindow *orig = global_state.callback_os_window; OSWindow *orig = global_state.callback_os_window;
@@ -695,7 +764,6 @@ update_ime_position_for_window(id_type window_id, bool force, int update_focus)
return false; return false;
} }
// Python API {{{ // Python API {{{
#define PYWRAP0(name) static PyObject* py##name(PYNOARG) #define PYWRAP0(name) static PyObject* py##name(PYNOARG)
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
@@ -705,6 +773,7 @@ update_ime_position_for_window(id_type window_id, bool force, int update_focus)
#define THREE_UINT(name) PYWRAP1(name) { unsigned int a, b, c; PA("III", &a, &b, &c); name(a, b, c); 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; }
#define TWO_ID(name) PYWRAP1(name) { id_type a, b; PA("KK", &a, &b); name(a, b); Py_RETURN_NONE; } #define TWO_ID(name) PYWRAP1(name) { id_type a, b; PA("KK", &a, &b); name(a, b); Py_RETURN_NONE; }
#define THREE_ID(name) PYWRAP1(name) { id_type a, b, c; PA("KKK", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; } #define THREE_ID(name) PYWRAP1(name) { id_type a, b, c; PA("KKK", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
#define FOUR_ID(name) PYWRAP1(name) { id_type a, b, c, d; PA("KKKK", &a, &b, &c, &d); name(a, b, c, d); Py_RETURN_NONE; }
#define THREE_ID_OBJ(name) PYWRAP1(name) { id_type a, b, c; PyObject *o; PA("KKKO", &a, &b, &c, &o); name(a, b, c, o); Py_RETURN_NONE; } #define THREE_ID_OBJ(name) PYWRAP1(name) { id_type a, b, c; PyObject *o; PA("KKKO", &a, &b, &c, &o); name(a, b, c, o); Py_RETURN_NONE; }
#define K(name) PYWRAP1(name) { id_type a; PA("K", &a); name(a); Py_RETURN_NONE; } #define K(name) PYWRAP1(name) { id_type a; PA("K", &a); name(a); Py_RETURN_NONE; }
#define KI(name) PYWRAP1(name) { id_type a; unsigned int b; PA("KI", &a, &b); name(a, b); Py_RETURN_NONE; } #define KI(name) PYWRAP1(name) { id_type a; unsigned int b; PA("KI", &a, &b); name(a, b); Py_RETURN_NONE; }
@@ -1399,6 +1468,7 @@ PYWRAP1(buffer_keys_in_window) {
THREE_ID_OBJ(update_window_title) THREE_ID_OBJ(update_window_title)
THREE_ID(remove_window) THREE_ID(remove_window)
FOUR_ID(remove_floating_window)
THREE_ID(detach_window) THREE_ID(detach_window)
THREE_ID(attach_window) THREE_ID(attach_window)
PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); } PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); }
@@ -1412,6 +1482,14 @@ KII(swap_tabs)
KK5I(add_borders_rect) KK5I(add_borders_rect)
KKKK(set_redirect_keys_to_overlay) KKKK(set_redirect_keys_to_overlay)
PYWRAP1(add_floating_window) {
id_type os_window_id, tab_id, floating_in;
PA("KKK", &os_window_id, &tab_id, &floating_in);
Window *ans = add_floating_window(os_window_id, tab_id, floating_in);
if (ans) return Py_BuildValue("KK", ans->id, ans->floating.non_floating_ancestor);
return Py_BuildValue("II", 0, 0);
}
static PyObject* static PyObject*
os_window_focus_counters(PyObject *self UNUSED, PyObject *args UNUSED) { os_window_focus_counters(PyObject *self UNUSED, PyObject *args UNUSED) {
RAII_PyObject(ans, PyDict_New()); RAII_PyObject(ans, PyDict_New());
@@ -1462,9 +1540,11 @@ static PyMethodDef module_methods[] = {
MW(pt_to_px, METH_VARARGS), MW(pt_to_px, METH_VARARGS),
MW(add_tab, METH_O), MW(add_tab, METH_O),
MW(add_window, METH_VARARGS), MW(add_window, METH_VARARGS),
MW(add_floating_window, METH_VARARGS),
MW(update_window_title, METH_VARARGS), MW(update_window_title, METH_VARARGS),
MW(remove_tab, METH_VARARGS), MW(remove_tab, METH_VARARGS),
MW(remove_window, METH_VARARGS), MW(remove_window, METH_VARARGS),
MW(remove_floating_window, METH_VARARGS),
MW(detach_window, METH_VARARGS), MW(detach_window, METH_VARARGS),
MW(attach_window, METH_VARARGS), MW(attach_window, METH_VARARGS),
MW(set_active_tab, METH_VARARGS), MW(set_active_tab, METH_VARARGS),

View File

@@ -189,7 +189,7 @@ typedef struct WindowBarData {
bool needs_render; bool needs_render;
} WindowBarData; } WindowBarData;
typedef struct { typedef struct Window {
id_type id; id_type id;
bool visible; bool visible;
float cursor_opacity_at_last_render; float cursor_opacity_at_last_render;
@@ -216,6 +216,12 @@ typedef struct {
PendingClick *clicks; PendingClick *clicks;
size_t num, capacity; size_t num, capacity;
} pending_clicks; } pending_clicks;
struct {
bool is_floating, can_become_key, is_key;
id_type parent, non_floating_ancestor;
struct Window *children; // must be maintained in render order
size_t child_count, child_capacity;
} floating;
} Window; } Window;
typedef struct { typedef struct {
@@ -440,3 +446,5 @@ bool render_os_window(OSWindow *w, monotonic_t now, bool ignore_render_frames, b
void update_mouse_pointer_shape(void); void update_mouse_pointer_shape(void);
void adjust_window_size_for_csd(OSWindow *w, int width, int height, int *adjusted_width, int *adjusted_height); void adjust_window_size_for_csd(OSWindow *w, int width, int height, int *adjusted_width, int *adjusted_height);
void dispatch_buffered_keys(Window *w); void dispatch_buffered_keys(Window *w);
Window* find_child_window(Window *self, id_type child_id);
void iter_child_windows(Window *self, bool(*callback)(Window *, void *data), void *data);

View File

@@ -568,20 +568,27 @@ class Tab: # {{{
pass_fds: tuple[int, ...] = (), pass_fds: tuple[int, ...] = (),
remote_control_fd: int = -1, remote_control_fd: int = -1,
next_to: Window | None = None, next_to: Window | None = None,
is_float: bool = False,
) -> Window: ) -> Window:
floating_in = 0
if is_float:
fp = next_to or self.active_window
if fp is not None:
floating_in = fp.id
child = self.launch_child( child = self.launch_child(
use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env, use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env,
is_clone_launch=is_clone_launch, add_listen_on_env_var=False if allow_remote_control and remote_control_passwords else True, is_clone_launch=is_clone_launch, add_listen_on_env_var=False if allow_remote_control and remote_control_passwords else True,
hold=hold, pass_fds=pass_fds, remote_control_fd=remote_control_fd, hold=hold, pass_fds=pass_fds, remote_control_fd=remote_control_fd,
) )
window = Window( window = Window(
self, child, self.args, override_title=override_title, self, child, self.args, override_title=override_title, floating_in=floating_in,
copy_colors_from=copy_colors_from, watchers=watchers, copy_colors_from=copy_colors_from, watchers=watchers,
allow_remote_control=allow_remote_control, remote_control_passwords=remote_control_passwords allow_remote_control=allow_remote_control, remote_control_passwords=remote_control_passwords
) )
# Must add child before laying out so that resize_pty succeeds # Must add child before laying out so that resize_pty succeeds
get_boss().add_child(window) get_boss().add_child(window)
self._add_window(window, location=location, overlay_for=overlay_for, overlay_behind=overlay_behind, bias=bias, next_to=next_to) if not floating_in:
self._add_window(window, location=location, overlay_for=overlay_for, overlay_behind=overlay_behind, bias=bias, next_to=next_to)
if marker: if marker:
try: try:
window.set_marker(marker) window.set_marker(marker)
@@ -843,7 +850,7 @@ class Tab: # {{{
yield w.as_dict( yield w.as_dict(
is_active=w is active_window, is_active=w is active_window,
is_focused=w.os_window_id == current_focused_os_window_id() and w is active_window, is_focused=w.os_window_id == current_focused_os_window_id() and w is active_window,
is_self=w is self_window) self_window=self_window)
def list_groups(self) -> list[dict[str, Any]]: def list_groups(self) -> list[dict[str, Any]]:
return [g.as_simple_dict() for g in self.windows.groups] return [g.as_simple_dict() for g in self.windows.groups]

View File

@@ -19,6 +19,7 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
Deque, Deque,
Iterator,
Literal, Literal,
NamedTuple, NamedTuple,
Optional, Optional,
@@ -54,6 +55,7 @@ from .fast_data_types import (
ColorProfile, ColorProfile,
KeyEvent, KeyEvent,
Screen, Screen,
add_floating_window,
add_timer, add_timer,
add_window, add_window,
base64_decode, base64_decode,
@@ -87,12 +89,13 @@ from .fast_data_types import (
update_window_visibility, update_window_visibility,
wakeup_main_loop, wakeup_main_loop,
) )
from .floats import calculate_geometry_for_float
from .keys import keyboard_mode_name, mod_mask from .keys import keyboard_mode_name, mod_mask
from .progress import Progress from .progress import Progress
from .rgb import to_color from .rgb import to_color
from .terminfo import get_capabilities from .terminfo import get_capabilities
from .types import MouseEvent, OverlayType, WindowGeometry, ac, run_once from .types import MouseEvent, OverlayType, WindowGeometry, ac, run_once
from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict, WindowType
from .utils import ( from .utils import (
color_as_int, color_as_int,
docs_url, docs_url,
@@ -243,6 +246,7 @@ class WindowDict(TypedDict):
at_prompt: bool at_prompt: bool
created_at: int created_at: int
in_alternate_screen: bool in_alternate_screen: bool
floats: tuple['WindowDict', ...]
class PipeData(TypedDict): class PipeData(TypedDict):
@@ -623,6 +627,7 @@ class Window:
watchers: Watchers | None = None, watchers: Watchers | None = None,
allow_remote_control: bool = False, allow_remote_control: bool = False,
remote_control_passwords: dict[str, Sequence[str]] | None = None, remote_control_passwords: dict[str, Sequence[str]] | None = None,
floating_in: int = 0, # window id of non-floating ancestor window
): ):
if watchers: if watchers:
self.watchers = watchers self.watchers = watchers
@@ -637,6 +642,7 @@ class Window:
self.started_at = monotonic() self.started_at = monotonic()
self.created_at = time_ns() self.created_at = time_ns()
self.clear_progress_timer: int = 0 self.clear_progress_timer: int = 0
self.floating_in = floating_in
self.current_remote_data: list[str] = [] self.current_remote_data: list[str] = []
self.current_mouse_event_button = 0 self.current_mouse_event_button = 0
self.current_clipboard_read_ask: bool | None = None self.current_clipboard_read_ask: bool | None = None
@@ -659,13 +665,21 @@ class Window:
self.child_title = self.default_title self.child_title = self.default_title
self.title_stack: Deque[str] = deque(maxlen=10) self.title_stack: Deque[str] = deque(maxlen=10)
self.user_vars: dict[str, str] = {} self.user_vars: dict[str, str] = {}
self.id: int = add_window(tab.os_window_id, tab.id, self.title) self.non_floating_ancestor: int = 0
self.id: int = 0
if floating_in:
self.id, self.non_floating_ancestor = add_floating_window(tab.os_window_id, tab.id, floating_in)
if not self.id:
raise Exception(f'No window with id: {floating_in} in Tab: {tab.id} OS Window: {tab.os_window_id} was found, or the window counter wrapped')
get_boss().window_floats_map.setdefault(floating_in, []).append(self.id)
else:
self.id = add_window(tab.os_window_id, tab.id, self.title)
if not self.id:
raise Exception(f'No tab with id: {tab.id} in OS Window: {tab.os_window_id} was found, or the window counter wrapped')
self.clipboard_request_manager = ClipboardRequestManager(self.id) self.clipboard_request_manager = ClipboardRequestManager(self.id)
self.margin = EdgeWidths() self.margin = EdgeWidths()
self.padding = EdgeWidths() self.padding = EdgeWidths()
self.kitten_result: dict[str, Any] | None = None self.kitten_result: dict[str, Any] | None = None
if not self.id:
raise Exception(f'No tab with id: {tab.id} in OS Window: {tab.os_window_id} was found, or the window counter wrapped')
self.tab_id = tab.id self.tab_id = tab.id
self.os_window_id = tab.os_window_id self.os_window_id = tab.os_window_id
self.tabref: Callable[[], TabType | None] = weakref.ref(tab) self.tabref: Callable[[], TabType | None] = weakref.ref(tab)
@@ -703,6 +717,8 @@ class Window:
self.tab_id = tab.id self.tab_id = tab.id
self.os_window_id = tab.os_window_id self.os_window_id = tab.os_window_id
self.tabref = weakref.ref(tab) self.tabref = weakref.ref(tab)
for cw in self.floats:
cw.change_tab(tab)
def effective_margin(self, edge: EdgeLiteral) -> int: def effective_margin(self, edge: EdgeLiteral) -> int:
q = getattr(self.margin, edge) q = getattr(self.margin, edge)
@@ -763,7 +779,16 @@ class Window:
def __repr__(self) -> str: def __repr__(self) -> str:
return f'Window(title={self.title}, id={self.id})' return f'Window(title={self.title}, id={self.id})'
def as_dict(self, is_focused: bool = False, is_self: bool = False, is_active: bool = False) -> WindowDict: @property
def floats(self) -> Iterator['Window']:
b = get_boss()
if b is not None:
for fid in b.window_floats_map.get(self.id, ()):
c = b.window_id_map.get(fid)
if c is not None:
yield c
def as_dict(self, is_focused: bool = False, self_window: WindowType | None = None, is_active: bool = False) -> WindowDict:
return { return {
'id': self.id, 'id': self.id,
'is_focused': is_focused, 'is_focused': is_focused,
@@ -776,13 +801,15 @@ class Window:
'last_cmd_exit_status': self.last_cmd_exit_status, 'last_cmd_exit_status': self.last_cmd_exit_status,
'env': self.child.environ or self.child.final_env, 'env': self.child.environ or self.child.final_env,
'foreground_processes': self.child.foreground_processes, 'foreground_processes': self.child.foreground_processes,
'is_self': is_self, 'is_self': self_window is self,
'at_prompt': self.at_prompt, 'at_prompt': self.at_prompt,
'lines': self.screen.lines, 'lines': self.screen.lines,
'columns': self.screen.columns, 'columns': self.screen.columns,
'user_vars': self.user_vars, 'user_vars': self.user_vars,
'created_at': self.created_at, 'created_at': self.created_at,
'in_alternate_screen': self.screen.is_using_alternate_linebuf(), 'in_alternate_screen': self.screen.is_using_alternate_linebuf(),
'floats': tuple(w.as_dict(
is_focused=is_focused and w.is_focused, is_active=is_active and w.is_focused, self_window=self_window) for w in self.floats),
} }
def serialize_state(self) -> dict[str, Any]: def serialize_state(self) -> dict[str, Any]:
@@ -803,6 +830,7 @@ class Window:
'margin': self.margin.serialize(), 'margin': self.margin.serialize(),
'user_vars': self.user_vars, 'user_vars': self.user_vars,
'padding': self.padding.serialize(), 'padding': self.padding.serialize(),
'in_alternate_screen': self.screen.is_using_alternate_linebuf(),
} }
if self.window_custom_type: if self.window_custom_type:
ans['window_custom_type'] = self.window_custom_type ans['window_custom_type'] = self.window_custom_type
@@ -810,6 +838,9 @@ class Window:
ans['overlay_type'] = self.overlay_type.value ans['overlay_type'] = self.overlay_type.value
if self.user_vars: if self.user_vars:
ans['user_vars'] = self.user_vars ans['user_vars'] = self.user_vars
floats = tuple(self.floats)
if floats:
ans['floats'] = floats
return ans return ans
@property @property
@@ -931,8 +962,8 @@ class Window:
self.screen.lines, self.screen.columns, self.screen.lines, self.screen.columns,
max(0, new_geometry.right - new_geometry.left), max(0, new_geometry.bottom - new_geometry.top)) max(0, new_geometry.right - new_geometry.left), max(0, new_geometry.bottom - new_geometry.top))
update_ime_position = False update_ime_position = False
boss = get_boss()
if current_pty_size != self.last_reported_pty_size: if current_pty_size != self.last_reported_pty_size:
boss = get_boss()
boss.child_monitor.resize_pty(self.id, *current_pty_size) boss.child_monitor.resize_pty(self.id, *current_pty_size)
self.last_resized_at = monotonic() self.last_resized_at = monotonic()
self.last_reported_pty_size = current_pty_size self.last_reported_pty_size = current_pty_size
@@ -948,12 +979,14 @@ class Window:
print(f'[{monotonic():.3f}] SIGWINCH sent to child in window: {self.id} with size: {current_pty_size}', file=sys.stderr) print(f'[{monotonic():.3f}] SIGWINCH sent to child in window: {self.id} with size: {current_pty_size}', file=sys.stderr)
else: else:
mark_os_window_dirty(self.os_window_id) mark_os_window_dirty(self.os_window_id)
self.geometry = new_geometry
self.geometry = g = new_geometry set_window_render_data(self.os_window_id, self.tab_id, self.id, self.screen, *self.geometry[:4])
set_window_render_data(self.os_window_id, self.tab_id, self.id, self.screen, *g[:4])
self.update_effective_padding() self.update_effective_padding()
if update_ime_position: if update_ime_position:
update_ime_position_for_window(self.id, True) update_ime_position_for_window(self.id, True)
for cw in self.floats:
child_geometry = calculate_geometry_for_float(cw, self.geometry)
cw.set_geometry(child_geometry)
def contains(self, x: int, y: int) -> bool: def contains(self, x: int, y: int) -> bool:
g = self.geometry g = self.geometry
@@ -1710,8 +1743,12 @@ class Window:
traceback.print_exc() traceback.print_exc()
def destroy(self) -> None: def destroy(self) -> None:
self.call_watchers(self.watchers.on_close, {}) if self.destroyed:
return
self.destroyed = True self.destroyed = True
for cw in self.floats:
cw.destroy()
self.call_watchers(self.watchers.on_close, {})
self.clipboard_request_manager.close() self.clipboard_request_manager.close()
del self.kitten_result_processors del self.kitten_result_processors
if hasattr(self, 'screen'): if hasattr(self, 'screen'):