mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-07 09:46:01 +02:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
715645e69c | ||
|
|
4e697abb34 | ||
|
|
96b0c463e8 |
@@ -91,7 +91,7 @@ class Borders:
|
||||
for tbr in tab_bar_rects:
|
||||
add_borders_rect(self.os_window_id, self.tab_id, *tbr)
|
||||
bw = 0
|
||||
groups = tuple(all_windows.iter_all_layoutable_groups(only_visible=True))
|
||||
groups = tuple(all_windows.iter_all_layoutable_groups())
|
||||
if groups:
|
||||
bw = groups[0].effective_border()
|
||||
draw_borders = bw > 0 and draw_window_borders
|
||||
|
||||
@@ -526,7 +526,7 @@ def monitor_pid(pid: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def add_window(os_window_id: int, tab_id: int, title: str) -> int:
|
||||
def add_window(os_window_id: int, tab_id: int, title: str, is_floating: bool) -> int:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from .clipboard import set_clipboard_string, set_primary_selection
|
||||
from .fast_data_types import add_timer, get_boss, get_options, get_os_window_title, patch_color_profiles
|
||||
from .options.utils import env as parse_env
|
||||
from .tabs import Tab, TabManager
|
||||
from .types import LayerShellConfig, OverlayType, run_once
|
||||
from .types import FloatType, LayerShellConfig, OverlayType, run_once
|
||||
from .utils import get_editor, log_error, resolve_custom_file, which
|
||||
from .window import CwdRequest, CwdRequestType, Watchers, Window
|
||||
|
||||
@@ -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,os-panel,overlay,overlay-main,background,clipboard,primary
|
||||
choices=window,tab,os-window,os-panel,overlay,overlay-main,float-in-window,float-in-tab,background,clipboard,primary
|
||||
Where to launch the child process:
|
||||
|
||||
:code:`window`
|
||||
@@ -105,6 +105,14 @@ 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-in-window`
|
||||
A floating window that is drawn over a non-floating window, usually the currently
|
||||
active window.
|
||||
|
||||
:code:`float-in-tab`
|
||||
A floating window that is drawn over all windows in a tab, usually the currently
|
||||
active tab.
|
||||
|
||||
: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
|
||||
@@ -753,8 +761,9 @@ 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):
|
||||
float_type = {'float-in-window': FloatType.window, 'float-in-tab': FloatType.tab}.get(opts.type, FloatType.none)
|
||||
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)
|
||||
env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, next_to=next_to, float_type=float_type, **kw)
|
||||
if child_death_callback is not None:
|
||||
boss.monitor_pid(new_window.child.pid or 0, child_death_callback)
|
||||
if spacing:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from collections.abc import Generator, Iterable, Iterator, Sequence
|
||||
from collections.abc import Generator, Iterable, Sequence
|
||||
from functools import partial
|
||||
from itertools import repeat
|
||||
from typing import Any, NamedTuple
|
||||
@@ -9,7 +9,7 @@ from typing import Any, NamedTuple
|
||||
from kitty.borders import BorderColor
|
||||
from kitty.fast_data_types import Region, set_active_window, viewport_for_window
|
||||
from kitty.options.types import Options
|
||||
from kitty.types import Edges, WindowGeometry
|
||||
from kitty.types import Edges, FloatType, WindowGeometry
|
||||
from kitty.typing_compat import TypedDict, WindowType
|
||||
from kitty.window_list import WindowGroup, WindowList
|
||||
|
||||
@@ -241,9 +241,9 @@ class Layout:
|
||||
|
||||
def bias_increment_for_cell(self, all_windows: WindowList, is_horizontal: bool) -> float:
|
||||
self._set_dimensions()
|
||||
return self.calculate_bias_increment_for_a_single_cell(all_windows, is_horizontal)
|
||||
return self.calculate_bias_increment_for_a_single_cell(is_horizontal)
|
||||
|
||||
def calculate_bias_increment_for_a_single_cell(self, all_windows: WindowList, is_horizontal: bool) -> float:
|
||||
def calculate_bias_increment_for_a_single_cell(self, is_horizontal: bool) -> float:
|
||||
if is_horizontal:
|
||||
return (lgd.cell_width + 1) / lgd.central.width
|
||||
return (lgd.cell_height + 1) / lgd.central.height
|
||||
@@ -284,7 +284,7 @@ class Layout:
|
||||
return self.neighbors_for_window(w, all_windows)
|
||||
|
||||
def move_window(self, all_windows: WindowList, delta: int = 1) -> bool:
|
||||
if all_windows.num_groups < 2 or not delta:
|
||||
if all_windows.num_of_non_floating_groups < 2 or not delta:
|
||||
return False
|
||||
|
||||
return all_windows.move_window_group(by=delta)
|
||||
@@ -332,7 +332,7 @@ class Layout:
|
||||
|
||||
def _bias_slot(self, all_windows: WindowList, idx: int, bias: float) -> bool:
|
||||
fractional_bias = max(10, min(abs(bias), 90)) / 100
|
||||
h, v = self.calculate_bias_increment_for_a_single_cell(all_windows, True), self.calculate_bias_increment_for_a_single_cell(all_windows, False)
|
||||
h, v = self.calculate_bias_increment_for_a_single_cell(True), self.calculate_bias_increment_for_a_single_cell(False)
|
||||
nh, nv = lgd.central.width / lgd.cell_width, lgd.central.height / lgd.cell_height
|
||||
f = max(-90, min(bias, 90)) / 100.
|
||||
return self.bias_slot(all_windows, idx, fractional_bias, h * nh *f, v * nv * f)
|
||||
@@ -341,10 +341,22 @@ class Layout:
|
||||
return False
|
||||
|
||||
def update_visibility(self, all_windows: WindowList) -> None:
|
||||
active_window = all_windows.active_window
|
||||
for window, is_group_leader in all_windows.iter_windows_with_visibility():
|
||||
is_visible = window is active_window or (is_group_leader and not self.only_active_window_visible)
|
||||
window.set_visible_in_layout(is_visible)
|
||||
active_non_floating_window = all_windows.active_non_floating_window
|
||||
floating_windows = []
|
||||
vismap = {}
|
||||
for window, is_group_leader, is_floating in all_windows.iter_windows_with_visibility():
|
||||
if is_floating:
|
||||
floating_windows.append((window, is_group_leader))
|
||||
else:
|
||||
is_visible = window is active_non_floating_window or (is_group_leader and not self.only_active_window_visible)
|
||||
window.set_visible_in_layout(is_visible)
|
||||
vismap[window.id] = is_visible
|
||||
for window, is_group_leader in sorted(floating_windows, key=lambda x: x[0].id):
|
||||
vis = is_group_leader
|
||||
if vis and window.float_type is FloatType.window:
|
||||
vis = vismap.get(window.floating_in_window, True) # if floating_in_window not found float in tab
|
||||
window.set_visible_in_layout(vis)
|
||||
vismap[window.id] = vis
|
||||
|
||||
def _set_dimensions(self) -> None:
|
||||
lgd.central, tab_bar, vw, vh, lgd.cell_width, lgd.cell_height = viewport_for_window(self.os_window_id)
|
||||
@@ -354,6 +366,7 @@ class Layout:
|
||||
self.update_visibility(all_windows)
|
||||
self.blank_rects = []
|
||||
self.do_layout(all_windows)
|
||||
self.layout_floats(all_windows)
|
||||
|
||||
def layout_single_window_group(self, wg: WindowGroup, add_blank_rects: bool = True) -> None:
|
||||
bw = 1 if self.must_draw_borders else 0
|
||||
@@ -372,7 +385,7 @@ class Layout:
|
||||
|
||||
def xlayout(
|
||||
self,
|
||||
groups: Iterator[WindowGroup],
|
||||
groups: Iterable[WindowGroup],
|
||||
bias: None | Sequence[float] | dict[int, float] = None,
|
||||
start: int | None = None,
|
||||
size: int | None = None,
|
||||
@@ -391,7 +404,7 @@ class Layout:
|
||||
|
||||
def ylayout(
|
||||
self,
|
||||
groups: Iterator[WindowGroup],
|
||||
groups: Iterable[WindowGroup],
|
||||
bias: None | Sequence[float] | dict[int, float] = None,
|
||||
start: int | None = None,
|
||||
size: int | None = None,
|
||||
@@ -417,6 +430,11 @@ class Layout:
|
||||
def do_layout(self, windows: WindowList) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def layout_floats(self, windows: WindowList) -> None:
|
||||
for gr in windows.iter_all_floating_groups():
|
||||
# TODO: actually position the float based on its properties
|
||||
self.layout_single_window_group(gr, False)
|
||||
|
||||
def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap:
|
||||
return {'left': [], 'right': [], 'top': [], 'bottom': []}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ class Grid(Layout):
|
||||
return 0, 0
|
||||
|
||||
def bias_slot(self, all_windows: WindowList, idx: int, fractional_bias: float, cell_increment_bias_h: float, cell_increment_bias_v: float) -> bool:
|
||||
num_windows = all_windows.num_groups
|
||||
num_windows = all_windows.num_layoutable_groups
|
||||
ncols, nrows, special_rows, special_col = calc_grid_size(num_windows)
|
||||
row_num, col_num = self.position_for_window_idx(idx, num_windows, ncols, nrows, special_rows, special_col)
|
||||
if row_num == 0:
|
||||
@@ -93,7 +93,7 @@ class Grid(Layout):
|
||||
return tuple(self.variable_layout(layout_func, num_windows, b)) == before_layout
|
||||
|
||||
def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool:
|
||||
num_windows = all_windows.num_groups
|
||||
num_windows = all_windows.num_layoutable_groups
|
||||
ncols, nrows, special_rows, special_col = calc_grid_size(num_windows)
|
||||
row_num, col_num = self.position_for_window_idx(idx, num_windows, ncols, nrows, special_rows, special_col)
|
||||
|
||||
@@ -151,7 +151,7 @@ class Grid(Layout):
|
||||
on_col_done(col_windows)
|
||||
|
||||
def do_layout(self, all_windows: WindowList) -> None:
|
||||
n = all_windows.num_groups
|
||||
n = all_windows.num_layoutable_groups
|
||||
if n == 1:
|
||||
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups()))
|
||||
return
|
||||
@@ -193,7 +193,7 @@ class Grid(Layout):
|
||||
position_window_in_grid_cell(window_idx, xl, yl)
|
||||
|
||||
def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]:
|
||||
n = all_windows.num_groups
|
||||
n = all_windows.num_layoutable_groups
|
||||
if not lgd.draw_minimal_borders or n < 2:
|
||||
return
|
||||
needs_borders_map = all_windows.compute_needs_borders_map(lgd.draw_active_borders)
|
||||
@@ -260,7 +260,7 @@ class Grid(Layout):
|
||||
yield BorderLine(edges, color)
|
||||
|
||||
def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap:
|
||||
n = all_windows.num_groups
|
||||
n = all_windows.num_layoutable_groups
|
||||
if n < 4:
|
||||
return neighbors_for_tall_window(1, window, all_windows)
|
||||
|
||||
|
||||
@@ -118,9 +118,10 @@ class Tall(Layout):
|
||||
return True
|
||||
|
||||
def variable_layout(self, all_windows: WindowList, biased_map: dict[int, float]) -> LayoutDimension:
|
||||
num = all_windows.num_groups - self.num_full_size_windows
|
||||
windows = tuple(all_windows.iter_all_layoutable_groups())
|
||||
num = len(windows) - self.num_full_size_windows
|
||||
bias = biased_map if num > 1 else None
|
||||
return self.perp_axis_layout(all_windows.iter_all_layoutable_groups(), bias=bias, offset=self.num_full_size_windows)
|
||||
return self.perp_axis_layout(windows, bias=bias, offset=self.num_full_size_windows)
|
||||
|
||||
def bias_slot(self, all_windows: WindowList, idx: int, fractional_bias: float, cell_increment_bias_h: float, cell_increment_bias_v: float) -> bool:
|
||||
if idx < len(self.main_bias):
|
||||
@@ -134,7 +135,7 @@ class Tall(Layout):
|
||||
return before_layout == after_layout
|
||||
|
||||
def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool:
|
||||
num_windows = all_windows.num_groups
|
||||
num_windows = all_windows.num_layoutable_groups
|
||||
if self.main_is_horizontal == is_horizontal:
|
||||
before_main_bias = self.main_bias
|
||||
ncols = self.num_full_size_windows + 1
|
||||
@@ -159,10 +160,10 @@ class Tall(Layout):
|
||||
return before != after
|
||||
|
||||
def simple_layout(self, all_windows: WindowList) -> Generator[tuple[WindowGroup, LayoutData, LayoutData, bool], None, None]:
|
||||
num = all_windows.num_groups
|
||||
is_fat = not self.main_is_horizontal
|
||||
mirrored = self.layout_opts.mirrored
|
||||
groups = tuple(all_windows.iter_all_layoutable_groups())
|
||||
num = len(groups)
|
||||
main_bias = self.main_bias[::-1] if mirrored else self.main_bias
|
||||
if mirrored:
|
||||
groups = tuple(reversed(groups))
|
||||
@@ -218,9 +219,10 @@ class Tall(Layout):
|
||||
yield wg, xl, yl, False
|
||||
|
||||
def do_layout(self, all_windows: WindowList) -> None:
|
||||
num = all_windows.num_groups
|
||||
windows = tuple(all_windows.iter_all_layoutable_groups())
|
||||
num = len(windows)
|
||||
if num == 1:
|
||||
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups()))
|
||||
self.layout_single_window_group(windows[0])
|
||||
return
|
||||
layouts = (self.simple_layout if num <= self.num_full_size_windows + 1 else self.full_layout)(all_windows)
|
||||
for wg, xl, yl, is_full_size in layouts:
|
||||
@@ -270,7 +272,7 @@ class Tall(Layout):
|
||||
return None
|
||||
|
||||
def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]:
|
||||
num = all_windows.num_groups
|
||||
num = all_windows.num_layoutable_groups
|
||||
if num < 2 or not lgd.draw_minimal_borders:
|
||||
return
|
||||
try:
|
||||
|
||||
@@ -68,9 +68,10 @@ class Vertical(Layout):
|
||||
perp_axis_layout = Layout.xlayout
|
||||
|
||||
def variable_layout(self, all_windows: WindowList, biased_map: dict[int, float]) -> LayoutDimension:
|
||||
num_windows = all_windows.num_groups
|
||||
windows = tuple(all_windows.iter_all_layoutable_groups())
|
||||
num_windows = len(windows)
|
||||
bias = biased_map if num_windows > 1 else None
|
||||
return self.main_axis_layout(all_windows.iter_all_layoutable_groups(), bias=bias)
|
||||
return self.main_axis_layout(windows, bias=bias)
|
||||
|
||||
def fixed_layout(self, wg: WindowGroup) -> LayoutDimension:
|
||||
return self.perp_axis_layout(iter((wg,)), border_mult=0 if lgd.draw_minimal_borders else 1)
|
||||
@@ -82,7 +83,7 @@ class Vertical(Layout):
|
||||
def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool:
|
||||
if self.main_is_horizontal != is_horizontal:
|
||||
return False
|
||||
num_windows = all_windows.num_groups
|
||||
num_windows = all_windows.num_layoutable_groups
|
||||
if num_windows < 2:
|
||||
return False
|
||||
before_layout = list(self.variable_layout(all_windows, self.biased_map))
|
||||
@@ -109,7 +110,7 @@ class Vertical(Layout):
|
||||
yield wg, xl, yl
|
||||
|
||||
def do_layout(self, all_windows: WindowList) -> None:
|
||||
window_count = all_windows.num_groups
|
||||
window_count = all_windows.num_layoutable_groups
|
||||
if window_count == 1:
|
||||
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups()))
|
||||
return
|
||||
@@ -117,7 +118,7 @@ class Vertical(Layout):
|
||||
self.set_window_group_geometry(wg, xl, yl)
|
||||
|
||||
def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]:
|
||||
window_count = all_windows.num_groups
|
||||
window_count = all_windows.num_layoutable_groups
|
||||
if window_count < 2 or not lgd.draw_minimal_borders:
|
||||
return
|
||||
yield from borders(self.generate_layout_data(all_windows), self.main_is_horizontal, all_windows)
|
||||
|
||||
@@ -1134,7 +1134,7 @@ draw_cells(ssize_t vao_idx, const WindowRenderData *srd, OSWindow *os_window, bo
|
||||
}
|
||||
bool use_premult = false;
|
||||
has_underlying_image |= grd.num_of_below_refs > 0 || grd.num_of_negative_refs > 0;
|
||||
if (os_window->is_semi_transparent) {
|
||||
if (os_window->is_semi_transparent || srd->float_data.is_floating) {
|
||||
if (has_underlying_image) { draw_cells_interleaved_premult(vao_idx, screen, os_window, &crd, grd, wl); use_premult = true; }
|
||||
else draw_cells_simple(vao_idx, screen, &crd, grd, os_window->is_semi_transparent);
|
||||
} else {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "cleanup.h"
|
||||
#include "options/to-c-generated.h"
|
||||
#include "iqsort.h"
|
||||
#include <math.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
@@ -288,10 +289,11 @@ set_window_logo(Window *w, const char *path, const ImageAnchorPosition pos, floa
|
||||
}
|
||||
|
||||
static void
|
||||
initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
|
||||
initialize_window(Window *w, PyObject *title, bool init_gpu_resources, bool is_floating) {
|
||||
w->id = ++global_state.window_id_counter;
|
||||
w->visible = true;
|
||||
w->title = title;
|
||||
w->render_data.float_data.is_floating = is_floating;
|
||||
Py_XINCREF(title);
|
||||
if (!set_window_logo(w, OPT(default_window_logo), OPT(window_logo_position), OPT(window_logo_alpha), true, NULL, 0)) {
|
||||
log_error("Failed to load default window logo: %s", OPT(default_window_logo));
|
||||
@@ -303,14 +305,23 @@ initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sort_windows_in_render_order(Window *windows, size_t count) {
|
||||
#define lt(a, b) (!a->render_data.float_data.is_floating && b->render_data.float_data.is_floating) || (a->render_data.float_data.is_floating == b->render_data.float_data.is_floating && a->id < b->id)
|
||||
QSORT(Window, windows, count, lt)
|
||||
#undef lt
|
||||
}
|
||||
|
||||
static id_type
|
||||
add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
|
||||
add_window(id_type os_window_id, id_type tab_id, PyObject *title, bool is_floating) {
|
||||
WITH_TAB(os_window_id, tab_id);
|
||||
ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true);
|
||||
make_os_window_context_current(osw);
|
||||
zero_at_i(tab->windows, tab->num_windows);
|
||||
initialize_window(tab->windows + tab->num_windows, title, true);
|
||||
return tab->windows[tab->num_windows++].id;
|
||||
initialize_window(tab->windows + tab->num_windows, title, true, is_floating);
|
||||
Window *ans = tab->windows + tab->num_windows++;
|
||||
sort_windows_in_render_order(tab->windows, tab->num_windows);
|
||||
return ans->id;
|
||||
END_WITH_TAB;
|
||||
return 0;
|
||||
}
|
||||
@@ -1290,13 +1301,14 @@ static PyObject*
|
||||
pycreate_mock_window(PyObject *self UNUSED, PyObject *args) {
|
||||
Screen *screen;
|
||||
PyObject *title = NULL;
|
||||
if (!PyArg_ParseTuple(args, "O|U", &screen, &title)) return NULL;
|
||||
int is_floating = 0;
|
||||
if (!PyArg_ParseTuple(args, "O|Up", &screen, &title, &is_floating)) return NULL;
|
||||
Window *w = PyMem_Calloc(sizeof(Window), 1);
|
||||
if (!w) return NULL;
|
||||
Py_INCREF(screen);
|
||||
PyObject *ans = PyCapsule_New(w, "Window", destroy_mock_window);
|
||||
if (ans != NULL) {
|
||||
initialize_window(w, title, false);
|
||||
initialize_window(w, title, false, is_floating);
|
||||
w->render_data.screen = screen;
|
||||
}
|
||||
return ans;
|
||||
@@ -1404,7 +1416,11 @@ THREE_ID(remove_window)
|
||||
THREE_ID(detach_window)
|
||||
THREE_ID(attach_window)
|
||||
PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); }
|
||||
PYWRAP1(add_window) { PyObject *title; id_type a, b; PA("KKO", &a, &b, &title); return PyLong_FromUnsignedLongLong(add_window(a, b, title)); }
|
||||
PYWRAP1(add_window) {
|
||||
PyObject *title; id_type a, b; int is_floating;
|
||||
PA("KKOp", &a, &b, &title, &is_floating);
|
||||
return PyLong_FromUnsignedLongLong(add_window(a, b, title, is_floating));
|
||||
}
|
||||
PYWRAP0(current_os_window) { OSWindow *w = current_os_window(); if (!w) Py_RETURN_NONE; return PyLong_FromUnsignedLongLong(w->id); }
|
||||
TWO_ID(remove_tab)
|
||||
KI(set_active_tab)
|
||||
|
||||
@@ -141,10 +141,15 @@ typedef struct WindowLogoRenderData {
|
||||
bool using_default;
|
||||
} WindowLogoRenderData;
|
||||
|
||||
typedef struct FloatRenderData {
|
||||
bool is_floating;
|
||||
} FloatRenderData;
|
||||
|
||||
typedef struct {
|
||||
ssize_t vao_idx;
|
||||
float xstart, ystart, dx, dy;
|
||||
Screen *screen;
|
||||
FloatRenderData float_data;
|
||||
} WindowRenderData;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -52,7 +52,7 @@ from .layout.base import Layout
|
||||
from .layout.interface import create_layout_object_for, evict_cached_layouts
|
||||
from .progress import ProgressState
|
||||
from .tab_bar import TabBar, TabBarData
|
||||
from .types import ac
|
||||
from .types import FloatType, ac
|
||||
from .typing_compat import EdgeLiteral, SessionTab, SessionType, TypedDict
|
||||
from .utils import cmdline_for_hold, log_error, platform_window_id, resolved_shell, shlex_split, which
|
||||
from .window import CwdRequest, Watchers, Window, WindowDict
|
||||
@@ -192,7 +192,7 @@ class Tab: # {{{
|
||||
def has_single_window_visible(self) -> bool:
|
||||
if self.current_layout.only_active_window_visible:
|
||||
return True
|
||||
for i, g in enumerate(self.windows.iter_all_layoutable_groups(only_visible=True)):
|
||||
for i, g in enumerate(self.windows.iter_all_layoutable_groups()):
|
||||
if i > 0:
|
||||
return False
|
||||
return True
|
||||
@@ -568,15 +568,21 @@ class Tab: # {{{
|
||||
pass_fds: tuple[int, ...] = (),
|
||||
remote_control_fd: int = -1,
|
||||
next_to: Window | None = None,
|
||||
float_type: FloatType = FloatType.none,
|
||||
) -> Window:
|
||||
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,
|
||||
)
|
||||
floating_in = 0
|
||||
if float_type is FloatType.window:
|
||||
w = next_to or self.active_window
|
||||
if w:
|
||||
floating_in = w.id
|
||||
window = Window(
|
||||
self, child, self.args, override_title=override_title,
|
||||
copy_colors_from=copy_colors_from, watchers=watchers,
|
||||
self, child, self.args, override_title=override_title, floating_in_window=floating_in,
|
||||
copy_colors_from=copy_colors_from, watchers=watchers, float_type=float_type,
|
||||
allow_remote_control=allow_remote_control, remote_control_passwords=remote_control_passwords
|
||||
)
|
||||
# Must add child before laying out so that resize_pty succeeds
|
||||
@@ -673,8 +679,8 @@ class Tab: # {{{
|
||||
if self.windows:
|
||||
if num < 0:
|
||||
self.windows.make_previous_group_active(-num)
|
||||
elif self.windows.num_groups:
|
||||
self.current_layout.activate_nth_window(self.windows, min(num, self.windows.num_groups - 1))
|
||||
elif self.windows.num_of_floating_groups_and_non_floating_groups:
|
||||
self.current_layout.activate_nth_window(self.windows, min(num, self.windows.num_of_floating_groups_and_non_floating_groups - 1))
|
||||
self.relayout_borders()
|
||||
|
||||
@ac('win', 'Focus the first window')
|
||||
@@ -893,7 +899,7 @@ class Tab: # {{{
|
||||
|
||||
@property
|
||||
def num_window_groups(self) -> int:
|
||||
return self.windows.num_groups
|
||||
return self.windows.num_of_non_floating_groups
|
||||
|
||||
def __contains__(self, window: Window) -> bool:
|
||||
return window in self.windows
|
||||
|
||||
@@ -27,6 +27,13 @@ class OverlayType(Enum):
|
||||
main = 'main'
|
||||
|
||||
|
||||
class FloatType(Enum):
|
||||
none = 'none'
|
||||
window = 'window'
|
||||
tab = 'tab'
|
||||
os_window = 'os_window'
|
||||
|
||||
|
||||
class ParsedShortcut(NamedTuple):
|
||||
mods: int
|
||||
key_name: str
|
||||
|
||||
@@ -91,7 +91,7 @@ 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 .types import FloatType, MouseEvent, OverlayType, WindowGeometry, ac, run_once
|
||||
from .typing_compat import BossType, ChildType, EdgeLiteral, TabType, TypedDict
|
||||
from .utils import (
|
||||
color_as_int,
|
||||
@@ -623,12 +623,16 @@ class Window:
|
||||
watchers: Watchers | None = None,
|
||||
allow_remote_control: bool = False,
|
||||
remote_control_passwords: dict[str, Sequence[str]] | None = None,
|
||||
float_type: FloatType = FloatType.none,
|
||||
floating_in_window: int = 0,
|
||||
):
|
||||
if watchers:
|
||||
self.watchers = watchers
|
||||
self.watchers.add(global_watchers())
|
||||
else:
|
||||
self.watchers = global_watchers().copy()
|
||||
self.float_type = float_type
|
||||
self.floating_in_window = floating_in_window
|
||||
self.keys_redirected_till_ready_from: int = 0
|
||||
self.last_focused_at = 0.
|
||||
self.is_focused: bool = False
|
||||
@@ -659,7 +663,7 @@ 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.id: int = add_window(tab.os_window_id, tab.id, self.title, self.is_floating)
|
||||
self.clipboard_request_manager = ClipboardRequestManager(self.id)
|
||||
self.margin = EdgeWidths()
|
||||
self.padding = EdgeWidths()
|
||||
@@ -682,6 +686,10 @@ class Window:
|
||||
self.remote_control_passwords = remote_control_passwords
|
||||
self.allow_remote_control = allow_remote_control
|
||||
|
||||
@property
|
||||
def is_floating(self) -> bool:
|
||||
return self.float_type is not FloatType.none
|
||||
|
||||
def remote_control_allowed(self, pcmd: dict[str, Any], extra_data: dict[str, Any]) -> bool:
|
||||
if not self.allow_remote_control:
|
||||
return False
|
||||
|
||||
@@ -9,7 +9,7 @@ from itertools import count
|
||||
from typing import Any, Deque, Union
|
||||
|
||||
from .fast_data_types import Color, get_options
|
||||
from .types import OverlayType, WindowGeometry
|
||||
from .types import FloatType, OverlayType, WindowGeometry
|
||||
from .typing_compat import EdgeLiteral, TabType, WindowType
|
||||
|
||||
WindowOrId = Union[WindowType, int]
|
||||
@@ -72,6 +72,13 @@ class WindowGroup:
|
||||
else:
|
||||
self.windows.append(window)
|
||||
|
||||
@property
|
||||
def is_floating(self) -> bool:
|
||||
for w in self.windows:
|
||||
if w.is_floating:
|
||||
return True
|
||||
return False
|
||||
|
||||
def move_window_to_top_of_group(self, window: WindowType) -> bool:
|
||||
try:
|
||||
idx = self.windows.index(window)
|
||||
@@ -100,22 +107,19 @@ class WindowGroup:
|
||||
}
|
||||
|
||||
def decoration(self, which: EdgeLiteral, border_mult: int = 1, is_single_window: bool = False) -> int:
|
||||
if not self.windows:
|
||||
return 0
|
||||
w = self.windows[0]
|
||||
return w.effective_margin(which) + w.effective_border() * border_mult + w.effective_padding(which)
|
||||
for w in self.windows:
|
||||
return w.effective_margin(which) + w.effective_border() * border_mult + w.effective_padding(which)
|
||||
return 0
|
||||
|
||||
def effective_padding(self, which: EdgeLiteral) -> int:
|
||||
if not self.windows:
|
||||
return 0
|
||||
w = self.windows[0]
|
||||
return w.effective_padding(which)
|
||||
for w in self.windows:
|
||||
return w.effective_padding(which)
|
||||
return 0
|
||||
|
||||
def effective_border(self) -> int:
|
||||
if not self.windows:
|
||||
return 0
|
||||
w = self.windows[0]
|
||||
return w.effective_border()
|
||||
for w in self.windows:
|
||||
return w.effective_border()
|
||||
return 0
|
||||
|
||||
def set_geometry(self, geom: WindowGeometry) -> None:
|
||||
for w in self.windows:
|
||||
@@ -123,22 +127,19 @@ class WindowGroup:
|
||||
|
||||
@property
|
||||
def default_bg(self) -> Color:
|
||||
if self.windows:
|
||||
w = self.windows[-1]
|
||||
for w in reversed(self.windows):
|
||||
return w.screen.color_profile.default_bg or get_options().background
|
||||
return get_options().background
|
||||
|
||||
@property
|
||||
def geometry(self) -> WindowGeometry | None:
|
||||
if self.windows:
|
||||
w = self.windows[-1]
|
||||
for w in reversed(self.windows):
|
||||
return w.geometry
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_visible_in_layout(self) -> bool:
|
||||
if self.windows:
|
||||
w = self.windows[-1]
|
||||
for w in reversed(self.windows):
|
||||
return w.is_visible_in_layout
|
||||
return False
|
||||
|
||||
@@ -202,7 +203,7 @@ class WindowList:
|
||||
|
||||
def set_active_group_idx(self, i: int, notify: bool = True) -> bool:
|
||||
changed = False
|
||||
if i != self._active_group_idx and 0 <= i < len(self.groups):
|
||||
if i != self._active_group_idx and 0 <= i < self.num_of_floating_groups_and_non_floating_groups:
|
||||
old_active_window = self.active_window
|
||||
g = self.active_group
|
||||
if g is not None:
|
||||
@@ -226,14 +227,42 @@ class WindowList:
|
||||
def change_tab(self, tab: TabType) -> None:
|
||||
self.tabref = weakref.ref(tab)
|
||||
|
||||
def iter_windows_with_visibility(self) -> Iterator[tuple[WindowType, bool]]:
|
||||
def iter_windows_with_visibility(self) -> Iterator[tuple[WindowType, bool, bool]]:
|
||||
for g in self.groups:
|
||||
aw = g.active_window_id
|
||||
is_floating = g.is_floating
|
||||
for window in g:
|
||||
yield window, window.id == aw
|
||||
yield window, window.id == aw, is_floating
|
||||
|
||||
def iter_all_layoutable_groups(self, only_visible: bool = False) -> Iterator[WindowGroup]:
|
||||
return iter(g for g in self.groups if g.is_visible_in_layout) if only_visible else iter(self.groups)
|
||||
def iter_all_layoutable_groups(self) -> Iterator[WindowGroup]:
|
||||
for g in self.groups:
|
||||
if g.is_visible_in_layout and not g.is_floating:
|
||||
yield g
|
||||
|
||||
def iter_all_floating_groups(self) -> Iterator[WindowGroup]:
|
||||
for g in self.groups:
|
||||
if g.is_floating:
|
||||
yield g
|
||||
|
||||
@property
|
||||
def num_layoutable_groups(self) -> int:
|
||||
ans = 0
|
||||
for g in self.groups:
|
||||
if g.is_visible_in_layout and not g.is_floating:
|
||||
ans += 1
|
||||
return ans
|
||||
|
||||
@property
|
||||
def num_of_non_floating_groups(self) -> int:
|
||||
ans = 0
|
||||
for g in self.groups:
|
||||
if not g.is_floating:
|
||||
ans += 1
|
||||
return ans
|
||||
|
||||
@property
|
||||
def num_of_floating_groups_and_non_floating_groups(self) -> int:
|
||||
return len(self.groups)
|
||||
|
||||
def iter_windows_with_number(self, only_visible: bool = True) -> Iterator[tuple[int, WindowType]]:
|
||||
for i, g in enumerate(self.groups):
|
||||
@@ -259,10 +288,6 @@ class WindowList:
|
||||
return
|
||||
self.set_active_group_idx(len(self.groups) - 1, notify=notify)
|
||||
|
||||
@property
|
||||
def num_groups(self) -> int:
|
||||
return len(self.groups)
|
||||
|
||||
def group_for_window(self, x: WindowOrId) -> WindowGroup | None:
|
||||
q = self.id_map[x] if isinstance(x, int) else x
|
||||
for g in self.groups:
|
||||
@@ -308,6 +333,31 @@ class WindowList:
|
||||
return self.id_map[self.groups[self.active_group_idx].active_window_id]
|
||||
return None
|
||||
|
||||
@property
|
||||
def active_non_floating_window(self) -> WindowType | None:
|
||||
w = self.active_window
|
||||
if w is None:
|
||||
return None
|
||||
if not w.is_floating:
|
||||
return w
|
||||
if w.float_type is FloatType.window:
|
||||
parent = self.id_map.get(w.floating_in_window)
|
||||
if parent is not None:
|
||||
g = self.group_for_window(parent)
|
||||
if g is not None:
|
||||
ans = self.id_map.get(g.active_window_id)
|
||||
if ans is not None:
|
||||
return ans
|
||||
# tab or os window float or parent window closed
|
||||
gid_map = {g.id: g for g in self.groups}
|
||||
for gid in reversed(self.active_group_history):
|
||||
g = gid_map.get(gid)
|
||||
if g is not None:
|
||||
w = self.id_map.get(g.active_window_id)
|
||||
if w is not None and not w.is_floating:
|
||||
return w
|
||||
return None
|
||||
|
||||
@property
|
||||
def active_group_main(self) -> WindowType | None:
|
||||
with suppress(Exception):
|
||||
@@ -343,22 +393,28 @@ class WindowList:
|
||||
|
||||
if group_of is not None:
|
||||
target_group = self.group_for_window(group_of)
|
||||
if target_group is None and next_to is not None:
|
||||
q = self.id_map[next_to] if isinstance(next_to, int) else next_to
|
||||
pos = -1
|
||||
for i, g in enumerate(self.groups):
|
||||
if q in g:
|
||||
pos = i
|
||||
break
|
||||
if pos > -1:
|
||||
if window.is_floating:
|
||||
if target_group is None:
|
||||
target_group = WindowGroup()
|
||||
self.groups.insert(pos + (0 if before else 1), target_group)
|
||||
if target_group is None:
|
||||
target_group = WindowGroup()
|
||||
if before:
|
||||
self.groups.insert(0, target_group)
|
||||
else:
|
||||
self.groups.append(target_group)
|
||||
else:
|
||||
if target_group is None and next_to is not None:
|
||||
q = self.id_map[next_to] if isinstance(next_to, int) else next_to
|
||||
pos = -1
|
||||
for i, g in enumerate(self.groups):
|
||||
if q in g:
|
||||
pos = i
|
||||
break
|
||||
if pos > -1:
|
||||
target_group = WindowGroup()
|
||||
self.groups.insert(pos + (0 if before else 1), target_group)
|
||||
if target_group is None:
|
||||
target_group = WindowGroup()
|
||||
if before:
|
||||
self.groups.insert(0, target_group)
|
||||
else:
|
||||
self.groups.append(target_group)
|
||||
self.move_floating_groups_to_end()
|
||||
|
||||
old_active_window = self.active_window
|
||||
target_group.add_window(window, head_of_group=head_of_group)
|
||||
@@ -372,6 +428,14 @@ class WindowList:
|
||||
self.notify_on_active_window_change(old_active_window, new_active_window)
|
||||
return target_group
|
||||
|
||||
def move_floating_groups_to_end(self) -> None:
|
||||
fg: list[WindowGroup] = []
|
||||
ng: list[WindowGroup] = []
|
||||
for g in self.groups:
|
||||
(fg if g.is_floating else ng).append(g)
|
||||
ng.extend(fg)
|
||||
self.groups = ng
|
||||
|
||||
def remove_window(self, x: WindowOrId) -> None:
|
||||
old_active_window = self.active_window
|
||||
q = self.id_map[x] if isinstance(x, int) else x
|
||||
@@ -398,8 +462,8 @@ class WindowList:
|
||||
|
||||
def active_window_in_nth_group(self, n: int, clamp: bool = False) -> WindowType | None:
|
||||
if clamp:
|
||||
n = max(0, min(n, self.num_groups - 1))
|
||||
if 0 <= n < self.num_groups:
|
||||
n = max(0, min(n, len(self.groups) - 1))
|
||||
if 0 <= n < len(self.groups):
|
||||
return self.id_map.get(self.groups[n].active_window_id)
|
||||
return None
|
||||
|
||||
@@ -410,14 +474,14 @@ class WindowList:
|
||||
return None
|
||||
|
||||
def activate_next_window_group(self, delta: int) -> None:
|
||||
self.set_active_group_idx(wrap_increment(self.active_group_idx, self.num_groups, delta))
|
||||
self.set_active_group_idx(wrap_increment(self.active_group_idx, self.num_of_floating_groups_and_non_floating_groups, delta))
|
||||
|
||||
def move_window_group(self, by: int | None = None, to_group: int | None = None) -> bool:
|
||||
if self.active_group_idx < 0 or not self.groups:
|
||||
return False
|
||||
target = -1
|
||||
if by is not None:
|
||||
target = wrap_increment(self.active_group_idx, self.num_groups, by)
|
||||
target = wrap_increment(self.active_group_idx, len(self.groups), by)
|
||||
if to_group is not None:
|
||||
for i, group in enumerate(self.groups):
|
||||
if group.id == to_group:
|
||||
@@ -427,13 +491,14 @@ class WindowList:
|
||||
if target == self.active_group_idx:
|
||||
return False
|
||||
self.groups[self.active_group_idx], self.groups[target] = self.groups[target], self.groups[self.active_group_idx]
|
||||
self.move_floating_groups_to_end()
|
||||
self.set_active_group_idx(target)
|
||||
return True
|
||||
return False
|
||||
|
||||
def compute_needs_borders_map(self, draw_active_borders: bool) -> dict[int, bool]:
|
||||
ag = self.active_group
|
||||
return {gr.id: ((gr is ag and draw_active_borders) or gr.needs_attention) for gr in self.groups}
|
||||
return {gr.id: ((gr is ag and draw_active_borders) or gr.needs_attention) for gr in self.groups if not gr.is_floating}
|
||||
|
||||
@property
|
||||
def num_visble_groups(self) -> int:
|
||||
|
||||
@@ -14,6 +14,7 @@ class Window:
|
||||
|
||||
def __init__(self, win_id, overlay_for=None, overlay_window_id=None):
|
||||
self.id = win_id
|
||||
self.is_floating = False
|
||||
self.overlay_for = overlay_for
|
||||
self.overlay_window_id = overlay_window_id
|
||||
self.is_visible_in_layout = True
|
||||
@@ -98,15 +99,15 @@ class TestLayout(BaseTest):
|
||||
check_visible()
|
||||
|
||||
# Test nth_window
|
||||
for i in range(windows.num_groups):
|
||||
for i in range(windows.num_of_floating_groups_and_non_floating_groups):
|
||||
q.activate_nth_window(windows, i)
|
||||
self.ae(windows.active_group_idx, i)
|
||||
expect_ids(*range(1, len(windows)+1))
|
||||
check_visible()
|
||||
|
||||
# Test next_window
|
||||
for i in range(2 * windows.num_groups):
|
||||
expected = (windows.active_group_idx + 1) % windows.num_groups
|
||||
for i in range(2 * windows.num_of_floating_groups_and_non_floating_groups):
|
||||
expected = (windows.active_group_idx + 1) % windows.num_of_floating_groups_and_non_floating_groups
|
||||
q.next_window(windows)
|
||||
self.ae(windows.active_group_idx, expected)
|
||||
expect_ids(*range(1, len(windows)+1))
|
||||
@@ -127,9 +128,9 @@ class TestLayout(BaseTest):
|
||||
# Test add_window
|
||||
windows.set_active_group_idx(4)
|
||||
q.add_window(windows, Window(6))
|
||||
self.ae(windows.num_groups, 6)
|
||||
self.ae(windows.num_of_floating_groups_and_non_floating_groups, 6)
|
||||
self.ae(windows.active_group_idx, 5)
|
||||
expect_ids(*range(1, windows.num_groups+1))
|
||||
expect_ids(*range(1, windows.num_of_floating_groups_and_non_floating_groups+1))
|
||||
check_visible()
|
||||
|
||||
# Test remove_window
|
||||
@@ -150,7 +151,7 @@ class TestLayout(BaseTest):
|
||||
expect_ids(2, 3, 5, 6)
|
||||
|
||||
# Test set_active_window
|
||||
for i in range(windows.num_groups):
|
||||
for i in range(windows.num_of_floating_groups_and_non_floating_groups):
|
||||
windows.set_active_group_idx(i)
|
||||
self.ae(i, windows.active_group_idx)
|
||||
check_visible()
|
||||
@@ -177,10 +178,10 @@ class TestLayout(BaseTest):
|
||||
w = Window(len(windows) + 1)
|
||||
windows.add_window(w)
|
||||
expect_ids(1, 2, 3, 4, 5, 6)
|
||||
self.ae(windows.active_group_idx, windows.num_groups - 1)
|
||||
self.ae(windows.active_group_idx, windows.num_of_floating_groups_and_non_floating_groups - 1)
|
||||
|
||||
# Test nth_window
|
||||
for i in range(windows.num_groups):
|
||||
for i in range(windows.num_of_floating_groups_and_non_floating_groups):
|
||||
q.activate_nth_window(windows, i)
|
||||
self.ae(windows.active_group_idx, i)
|
||||
if i == overlaid_group:
|
||||
@@ -189,8 +190,8 @@ class TestLayout(BaseTest):
|
||||
check_visible()
|
||||
|
||||
# Test next_window
|
||||
for i in range(windows.num_groups):
|
||||
expected = (windows.active_group_idx + 1) % windows.num_groups
|
||||
for i in range(windows.num_of_floating_groups_and_non_floating_groups):
|
||||
expected = (windows.active_group_idx + 1) % windows.num_of_floating_groups_and_non_floating_groups
|
||||
q.next_window(windows)
|
||||
self.ae(windows.active_group_idx, expected)
|
||||
expect_ids(1, 2, 3, 4, 5, 6)
|
||||
@@ -210,7 +211,7 @@ class TestLayout(BaseTest):
|
||||
check_visible()
|
||||
|
||||
# Test set_active_window
|
||||
for i in range(windows.num_groups):
|
||||
for i in range(windows.num_of_floating_groups_and_non_floating_groups):
|
||||
windows.set_active_group_idx(i)
|
||||
self.ae(i, windows.active_group_idx)
|
||||
if i == overlaid_group:
|
||||
|
||||
Reference in New Issue
Block a user