Compare commits

...

9 Commits

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_font_size,
redirect_mouse_handling,
remove_floating_window,
ring_bell,
run_with_activation_token,
safe_pipe,
@@ -360,6 +361,7 @@ class Boss:
self.clipboard = Clipboard()
self.window_for_dispatch: Window | None = None
self.primary_selection = Clipboard(ClipboardType.primary_selection)
self.window_floats_map: dict[int, list[int]] = {}
self.update_check_started = False
self.peer_data_map: dict[int, dict[str, Sequence[str]] | 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():
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:
prev_active_window = self.active_window
window = self.window_id_map.pop(window_id, None)
@@ -921,16 +928,25 @@ class Boss:
traceback.print_exc()
os_window_id = window.os_window_id
window.destroy()
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 cid in tuple(self.descendant_window_ids(window.id)):
self.window_floats_map.pop(cid, None)
cw = self.window_id_map.pop(cid, None)
if cw is not None:
self.child_monitor.mark_for_close(cw.id)
self.window_floats_map.pop(window.id, None)
if window.floating_in:
remove_floating_window(window.os_window_id, window.tab_id, window.floating_in, window.id)
else:
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:
try:
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:
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)
if tm is not None:
tm.destroy()
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):
tm.destroy() # this will call destroy on all windows
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_floats_map.pop(window_id, None)
w.destroy() # in case tm was None
if not self.os_window_map and is_macos:
cocoa_set_menubar_title('')
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;
}
static PyObject *
mark_for_close(ChildMonitor *self, PyObject *args) {
#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
}
#define WD w->render_data
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
bool needs_render = os_window->needs_render;
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;
for (unsigned int i = 0; i < tab->num_windows; i++) {
Window *w = tab->windows + i;
#define WD w->render_data
if (w->visible && WD.screen) {
screen_check_pause_rendering(WD.screen, now);
*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;
const bool is_active_non_floating_window = i == tab->active_window;
if (*num_visible_windows == 1) first_window_bg = window_bg;
if (first_window_bg != window_bg) *all_windows_have_same_bg = 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);
if (is_active_non_floating_window) *active_window_bg = window_bg;
needs_render |= prepare_to_render_window(os_window, tab, w, is_active_non_floating_window, now, scan_for_animated_images, active_window_id);
for (size_t i = 0; i < w->floating.child_count; i++) {
Window *cw = w->floating.children + i;
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);
}
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;
@@ -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
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
@@ -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);
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);
unsigned int num_of_visible_windows = 0;
unsigned num_of_visible_windows = 0;
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++) {
Window *w = tab->windows + i;
if (w->visible && WD.screen) {
bool is_active_window = i == tab->active_window;
if (is_active_window) active_window = w;
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;
if (draw_window(os_window, w, i == tab->active_window, num_of_visible_windows)) active_window = w;
for (size_t f = 0; f < w->floating.child_count; f++) {
Window *cw = w->floating.children + f;
if (cw->render_data.screen && draw_window(os_window, cw, i == tab->active_window, num_of_visible_windows)) active_window = cw;
}
}
}
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;
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;
color_type active_window_bg = 0;
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
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;
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;
}
destroy_os_window(os_window);
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);
}
static bool
process_pending_closes(ChildMonitor *self) {
process_pending_closes(void) {
if (global_state.quit_request == CONFIRMABLE_CLOSE_REQUESTED) {
call_boss(quit, "");
}
@@ -1135,14 +1153,14 @@ process_pending_closes(ChildMonitor *self) {
os_window->close_request = CLOSE_BEING_CONFIRMED;
call_boss(confirm_os_window_close, "K", os_window->id);
if (os_window->close_request == IMPERATIVE_CLOSE_REQUESTED) {
close_os_window(self, os_window);
close_os_window(os_window);
} else has_open_windows = true;
break;
case CLOSE_BEING_CONFIRMED:
has_open_windows = true;
break;
case IMPERATIVE_CLOSE_REQUESTED:
close_os_window(self, os_window);
close_os_window(os_window);
break;
}
}
@@ -1297,7 +1315,7 @@ process_global_state(void *data) {
#endif
report_reaped_pids();
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) {
stop_main_loop();
} 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
) -> bytes: ...
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):
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=choices
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:
: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
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`
The process will be run in the :italic:`background`, without a kitty
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
type=bool-set
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
@@ -722,8 +726,8 @@ def _launch(
tab = tab_for_window(boss, opts, target_tab, next_to)
watchers = load_watch_modules(opts.watcher)
with Window.set_ignore_focus_changes_for_new_windows(opts.keep_focus):
new_window: Window = tab.new_window(
env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, next_to=next_to, **kw)
new_window = tab.new_window(
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:
patch_window_edges(new_window, spacing)
tab.relayout()

View File

@@ -12,7 +12,7 @@
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++) { \
if (array[i].id == qid) { \
destroy(array + i); \
@@ -315,9 +315,31 @@ add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
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
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);
window->title = 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);
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
remove_window_inner(Tab *tab, id_type id) {
id_type active_window_id = 0;
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) {
for (unsigned int w = 0; w < tab->num_windows; w++) {
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
remove_window(id_type os_window_id, id_type tab_id, id_type 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;
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);
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) {
for (unsigned int i = 0; i < os_window->num_tabs; i++) {
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
if (found) {
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
update_os_window_references();
}
@@ -643,6 +691,26 @@ make_window_context_current(id_type window_id) {
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
dispatch_pending_clicks(id_type timer_id UNUSED, void *data UNUSED) {
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;
for (size_t w = 0; w < qtab->num_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.
if ((window->render_data.screen && (force || osw->is_focused)) || update_focus > 0) {
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;
}
// Python API {{{
#define PYWRAP0(name) static PyObject* py##name(PYNOARG)
#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 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 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 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; }
@@ -1399,6 +1468,7 @@ PYWRAP1(buffer_keys_in_window) {
THREE_ID_OBJ(update_window_title)
THREE_ID(remove_window)
FOUR_ID(remove_floating_window)
THREE_ID(detach_window)
THREE_ID(attach_window)
PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); }
@@ -1412,6 +1482,14 @@ KII(swap_tabs)
KK5I(add_borders_rect)
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*
os_window_focus_counters(PyObject *self UNUSED, PyObject *args UNUSED) {
RAII_PyObject(ans, PyDict_New());
@@ -1462,9 +1540,11 @@ static PyMethodDef module_methods[] = {
MW(pt_to_px, METH_VARARGS),
MW(add_tab, METH_O),
MW(add_window, METH_VARARGS),
MW(add_floating_window, METH_VARARGS),
MW(update_window_title, METH_VARARGS),
MW(remove_tab, METH_VARARGS),
MW(remove_window, METH_VARARGS),
MW(remove_floating_window, METH_VARARGS),
MW(detach_window, METH_VARARGS),
MW(attach_window, METH_VARARGS),
MW(set_active_tab, METH_VARARGS),

View File

@@ -189,7 +189,7 @@ typedef struct WindowBarData {
bool needs_render;
} WindowBarData;
typedef struct {
typedef struct Window {
id_type id;
bool visible;
float cursor_opacity_at_last_render;
@@ -216,6 +216,12 @@ typedef struct {
PendingClick *clicks;
size_t num, capacity;
} 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;
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 adjust_window_size_for_csd(OSWindow *w, int width, int height, int *adjusted_width, int *adjusted_height);
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, ...] = (),
remote_control_fd: int = -1,
next_to: Window | None = None,
is_float: bool = False,
) -> 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(
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,
hold=hold, pass_fds=pass_fds, remote_control_fd=remote_control_fd,
)
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,
allow_remote_control=allow_remote_control, remote_control_passwords=remote_control_passwords
)
# Must add child before laying out so that resize_pty succeeds
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:
try:
window.set_marker(marker)
@@ -843,7 +850,7 @@ class Tab: # {{{
yield w.as_dict(
is_active=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]]:
return [g.as_simple_dict() for g in self.windows.groups]

View File

@@ -19,6 +19,7 @@ from typing import (
TYPE_CHECKING,
Any,
Deque,
Iterator,
Literal,
NamedTuple,
Optional,
@@ -54,6 +55,7 @@ from .fast_data_types import (
ColorProfile,
KeyEvent,
Screen,
add_floating_window,
add_timer,
add_window,
base64_decode,
@@ -87,12 +89,13 @@ from .fast_data_types import (
update_window_visibility,
wakeup_main_loop,
)
from .floats import calculate_geometry_for_float
from .keys import keyboard_mode_name, mod_mask
from .progress import Progress
from .rgb import to_color
from .terminfo import get_capabilities
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 (
color_as_int,
docs_url,
@@ -243,6 +246,7 @@ class WindowDict(TypedDict):
at_prompt: bool
created_at: int
in_alternate_screen: bool
floats: tuple['WindowDict', ...]
class PipeData(TypedDict):
@@ -623,6 +627,7 @@ class Window:
watchers: Watchers | None = None,
allow_remote_control: bool = False,
remote_control_passwords: dict[str, Sequence[str]] | None = None,
floating_in: int = 0, # window id of non-floating ancestor window
):
if watchers:
self.watchers = watchers
@@ -637,6 +642,7 @@ class Window:
self.started_at = monotonic()
self.created_at = time_ns()
self.clear_progress_timer: int = 0
self.floating_in = floating_in
self.current_remote_data: list[str] = []
self.current_mouse_event_button = 0
self.current_clipboard_read_ask: bool | None = None
@@ -659,13 +665,21 @@ class Window:
self.child_title = self.default_title
self.title_stack: Deque[str] = deque(maxlen=10)
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.margin = EdgeWidths()
self.padding = EdgeWidths()
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.os_window_id = tab.os_window_id
self.tabref: Callable[[], TabType | None] = weakref.ref(tab)
@@ -703,6 +717,8 @@ class Window:
self.tab_id = tab.id
self.os_window_id = tab.os_window_id
self.tabref = weakref.ref(tab)
for cw in self.floats:
cw.change_tab(tab)
def effective_margin(self, edge: EdgeLiteral) -> int:
q = getattr(self.margin, edge)
@@ -763,7 +779,16 @@ class Window:
def __repr__(self) -> str:
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 {
'id': self.id,
'is_focused': is_focused,
@@ -776,13 +801,15 @@ class Window:
'last_cmd_exit_status': self.last_cmd_exit_status,
'env': self.child.environ or self.child.final_env,
'foreground_processes': self.child.foreground_processes,
'is_self': is_self,
'is_self': self_window is self,
'at_prompt': self.at_prompt,
'lines': self.screen.lines,
'columns': self.screen.columns,
'user_vars': self.user_vars,
'created_at': self.created_at,
'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]:
@@ -803,6 +830,7 @@ class Window:
'margin': self.margin.serialize(),
'user_vars': self.user_vars,
'padding': self.padding.serialize(),
'in_alternate_screen': self.screen.is_using_alternate_linebuf(),
}
if 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
if self.user_vars:
ans['user_vars'] = self.user_vars
floats = tuple(self.floats)
if floats:
ans['floats'] = floats
return ans
@property
@@ -931,8 +962,8 @@ class Window:
self.screen.lines, self.screen.columns,
max(0, new_geometry.right - new_geometry.left), max(0, new_geometry.bottom - new_geometry.top))
update_ime_position = False
boss = get_boss()
if current_pty_size != self.last_reported_pty_size:
boss = get_boss()
boss.child_monitor.resize_pty(self.id, *current_pty_size)
self.last_resized_at = monotonic()
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)
else:
mark_os_window_dirty(self.os_window_id)
self.geometry = g = new_geometry
set_window_render_data(self.os_window_id, self.tab_id, self.id, self.screen, *g[:4])
self.geometry = new_geometry
set_window_render_data(self.os_window_id, self.tab_id, self.id, self.screen, *self.geometry[:4])
self.update_effective_padding()
if update_ime_position:
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:
g = self.geometry
@@ -1710,8 +1743,12 @@ class Window:
traceback.print_exc()
def destroy(self) -> None:
self.call_watchers(self.watchers.on_close, {})
if self.destroyed:
return
self.destroyed = True
for cw in self.floats:
cw.destroy()
self.call_watchers(self.watchers.on_close, {})
self.clipboard_request_manager.close()
del self.kitten_result_processors
if hasattr(self, 'screen'):