mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 02:06:16 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d826c37b9 | ||
|
|
e3fa7fc60e | ||
|
|
77d2159d3f | ||
|
|
da753c3f5a | ||
|
|
d8889a2f08 | ||
|
|
14ff29426d | ||
|
|
a93a6af025 | ||
|
|
d5398f84d8 | ||
|
|
08d90db4c4 |
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
10
kitty/floats.py
Normal 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
|
||||
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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'):
|
||||
|
||||
Reference in New Issue
Block a user