mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-15 21:17:49 +02:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
715645e69c | ||
|
|
4e697abb34 | ||
|
|
96b0c463e8 | ||
|
|
f2412cc9c8 | ||
|
|
5dbd198fb9 | ||
|
|
9b4643e8bc | ||
|
|
f2f914ed05 | ||
|
|
ec5062ceb4 | ||
|
|
eb70f81218 | ||
|
|
04a4d62859 | ||
|
|
2aaa440519 | ||
|
|
782e7cb2fb | ||
|
|
41b44fdafa | ||
|
|
0f3603c0eb | ||
|
|
8ba9bfd460 | ||
|
|
22fd548903 | ||
|
|
7856046382 | ||
|
|
f93d57d919 | ||
|
|
e8d50d0734 |
@@ -106,12 +106,12 @@ consumption to do the same tasks.
|
||||
Detailed list of changes
|
||||
-------------------------------------
|
||||
|
||||
0.42.0 [future]
|
||||
0.42.0 [2025-05-11]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- A new kitten: :doc:`quick-access-terminal </kittens/quick-access-terminal>` to :ref:`quake`
|
||||
|
||||
- The :doc:`panel kitten </kittens/panel>` now works on macOS as well as Wayland (:iss:`2590`)
|
||||
- The :doc:`panel kitten </kittens/panel>` works on macOS and X11 as well as Wayland (:iss:`2590`)
|
||||
|
||||
- **Behavior change**: Now kitty does full grapheme segmentation following the
|
||||
Unicode 16 spec when splitting text into cells (:iss:`8533`)
|
||||
@@ -123,7 +123,7 @@ Detailed list of changes
|
||||
- launch: Allow creating desktop panels such as those created by the :doc:`panel kitten </kittens/panel>` (:iss:`8549`)
|
||||
|
||||
- Remote control: Allow modifying desktop panels and showing/hiding OS Windows
|
||||
using the `kitten @ resize-os-window` command (:iss:`8550`)
|
||||
using the ``kitten @ resize-os-window`` command (:iss:`8550`)
|
||||
|
||||
- Remote control launch: Allow waiting for a program launched in a new window
|
||||
to exit and get the exit code via the `kitty +launch
|
||||
@@ -160,7 +160,7 @@ Detailed list of changes
|
||||
|
||||
- :ac:`change_font_size` allow multiplying/dividing the current font size in addition to incrementing it (:iss:`8616`)
|
||||
|
||||
- Box drawing: Improve appearance of rounder corners giving them a uniform line width (:iss:`8299`)
|
||||
- Box drawing: Improve appearance of rounder corners, giving them a uniform line width (:iss:`8299`)
|
||||
|
||||
0.41.1 [2025-04-03]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,7 +22,7 @@ class Version(NamedTuple):
|
||||
|
||||
appname: str = 'kitty'
|
||||
kitty_face = '🐱'
|
||||
version: Version = Version(0, 41, 1)
|
||||
version: Version = Version(0, 42, 0)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
@@ -592,16 +592,27 @@ typedef struct CubicBezier {
|
||||
} CubicBezier;
|
||||
|
||||
#define bezier_eq(which) { \
|
||||
double tm1 = 1 - t; \
|
||||
double tm1_3 = tm1 * tm1 * tm1; \
|
||||
double t_3 = t * t * t; \
|
||||
return tm1_3 * cb.start.which + 3 * t * tm1 * (tm1 * cb.c1.which + t * cb.c2.which) + t_3 * cb.end.which; \
|
||||
const CubicBezier *cb = v; \
|
||||
const double u = 1. - t; \
|
||||
const double u_3 = u * u * u; \
|
||||
const double t_3 = t * t * t; \
|
||||
return u_3 * cb->start.which + 3 * t * u * (u * cb->c1.which + t * cb->c2.which) + t_3 * cb->end.which; \
|
||||
}
|
||||
static double
|
||||
bezier_x(CubicBezier cb, double t) { bezier_eq(x); }
|
||||
static double
|
||||
bezier_y(CubicBezier cb, double t) { bezier_eq(y); }
|
||||
|
||||
#define bezier_prime_eq(which) { \
|
||||
const CubicBezier *cb = v; \
|
||||
const double u = 1. - t; \
|
||||
const double u_2 = u * u; \
|
||||
const double t_2 = t * t; \
|
||||
return 3 * u_2 * (cb->c1.which - cb->start.which) + 6 * t * u * (cb->c2.which - cb->c1.which) + 3 * t_2 * (cb->end.which - cb->c2.which); \
|
||||
}
|
||||
|
||||
static double bezier_x(const void *v, double t) { bezier_eq(x); }
|
||||
static double bezier_y(const void *v, double t) { bezier_eq(y); }
|
||||
static double bezier_prime_x(const void *v, double t) { bezier_prime_eq(x); }
|
||||
static double bezier_prime_y(const void *v, double t) { bezier_prime_eq(y); }
|
||||
#undef bezier_eq
|
||||
#undef bezier_prime_eq
|
||||
|
||||
static int
|
||||
find_bezier_for_D(int width, int height) {
|
||||
@@ -609,13 +620,13 @@ find_bezier_for_D(int width, int height) {
|
||||
CubicBezier cb = {.end={.x=0, .y=height - 1}, .c2={.x=0, .y=height - 1}};
|
||||
while (true) {
|
||||
cb.c1.x = cx; cb.c2.x = cx;
|
||||
if (bezier_x(cb, 0.5) > width - 1) return last_cx;
|
||||
if (bezier_x(&cb, 0.5) > width - 1) return last_cx;
|
||||
last_cx = cx++;
|
||||
}
|
||||
}
|
||||
|
||||
static double
|
||||
find_t_for_x(CubicBezier cb, int x, double start_t) {
|
||||
find_t_for_x(const CubicBezier *cb, int x, double start_t) {
|
||||
if (fabs(bezier_x(cb, start_t) - x) < 0.1) return start_t;
|
||||
static const double t_limit = 0.5;
|
||||
double increment = t_limit - start_t;
|
||||
@@ -639,7 +650,7 @@ find_t_for_x(CubicBezier cb, int x, double start_t) {
|
||||
|
||||
|
||||
static void
|
||||
get_bezier_limits(Canvas *self, CubicBezier cb) {
|
||||
get_bezier_limits(Canvas *self, const CubicBezier *cb) {
|
||||
int start_x = (int)bezier_x(cb, 0), max_x = (int)bezier_x(cb, 0.5);
|
||||
double last_t = 0.;
|
||||
for (int x = start_x; x < max_x + 1; x++) {
|
||||
@@ -670,40 +681,11 @@ static void
|
||||
filled_D(Canvas *self, bool left) {
|
||||
int c1x = find_bezier_for_D(self->width, self->height);
|
||||
CubicBezier cb = {.end={.y=self->height-1}, .c1 = {.x=c1x}, .c2 = {.x=c1x, .y=self->height - 1}};
|
||||
get_bezier_limits(self, cb);
|
||||
get_bezier_limits(self, &cb);
|
||||
if (left) fill_region(self, false);
|
||||
else mirror_horizontally(fill_region(self, false));
|
||||
}
|
||||
|
||||
#define NAME position_set
|
||||
#define KEY_TY Point
|
||||
#define HASH_FN hash_point
|
||||
#define CMPR_FN cmpr_point
|
||||
static uint64_t hash_point(Point p);
|
||||
static bool cmpr_point(Point, Point);
|
||||
#include "kitty-verstable.h"
|
||||
static uint64_t hash_point(Point p) { return vt_hash_integer(p.val); }
|
||||
static bool cmpr_point(Point a, Point b) { return a.val == b.val; }
|
||||
|
||||
#define draw_parametrized_curve(self, level, xfunc, yfunc) { \
|
||||
div_t d = div(thickness(self, level, true), 2u); \
|
||||
int delta = d.quot, extra = d.rem; \
|
||||
uint num_samples = self->height * 8; \
|
||||
position_set seen; vt_init(&seen); \
|
||||
for (uint i = 0; i < num_samples + 1; i++) { \
|
||||
double t = i / (double)num_samples; \
|
||||
Point p = {.x=(int32_t)xfunc, .y=(int32_t)yfunc}; \
|
||||
position_set_itr q = vt_get(&seen, p); \
|
||||
if (!vt_is_end(q)) continue; \
|
||||
if (vt_is_end(vt_insert(&seen, p))) fatal("Out of memory"); \
|
||||
for (int y = MAX(0, p.y - delta); y < MIN(p.y + delta + extra, (int)self->height); y++) { \
|
||||
uint offset = y * self->width, start = MAX(0, p.x - delta); \
|
||||
memset(self->mask + offset + start, 255, minus((uint)MIN(p.x + delta + extra, (int)self->width), start)); \
|
||||
} \
|
||||
} \
|
||||
vt_cleanup(&seen); \
|
||||
}
|
||||
|
||||
static double
|
||||
distance(double x1, double y1, double x2, double y2) {
|
||||
const double dx = x1 - x2;
|
||||
@@ -711,27 +693,28 @@ distance(double x1, double y1, double x2, double y2) {
|
||||
return sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
typedef double(*curve_func)(void *, double t);
|
||||
typedef double(*curve_func)(const void *, double t);
|
||||
|
||||
static void
|
||||
draw_parametrized_curve_with_derivative(
|
||||
Canvas *self, void *curve_data, uint level, curve_func xfunc, curve_func yfunc, curve_func x_prime, curve_func y_prime,
|
||||
int x_offset, double thickness_fudge
|
||||
Canvas *self, void *curve_data, double line_width, curve_func xfunc, curve_func yfunc, curve_func x_prime, curve_func y_prime,
|
||||
int x_offset, int yoffset, double thickness_fudge
|
||||
) {
|
||||
const double th = thickness_as_float(self, level, true);
|
||||
double step = 1.0 / self->height;
|
||||
const double min_step = 1.0 / (100 * self->height);
|
||||
const double half_thickness = th / 2.0;
|
||||
double larger_dim = fmax(self->height, self->width);
|
||||
double step = 1.0 / larger_dim;
|
||||
const double min_step = step / 100., max_step = step;
|
||||
line_width = fmax(1., line_width);
|
||||
const double half_thickness = line_width / 2.0;
|
||||
const double distance_limit = half_thickness + thickness_fudge;
|
||||
double t = 0;
|
||||
while(true) {
|
||||
double x = xfunc(curve_data, t), y = yfunc(curve_data, t);
|
||||
for (double dy = -th; dy <= th; dy++) {
|
||||
for (double dx = -th; dx <= th; dx++) {
|
||||
for (double dy = -line_width; dy <= line_width; dy++) {
|
||||
for (double dx = -line_width; dx <= line_width; dx++) {
|
||||
double px = x + dx, py = y + dy;
|
||||
double dist = distance(x, y, px, py);
|
||||
int row = (int)py, col = (int)px + x_offset;
|
||||
if (dist >= distance_limit || row >= (int)self->height || row < 0 || col >= (int)self->width || col < 0) continue;
|
||||
int row = (int)py + yoffset, col = (int)px + x_offset;
|
||||
if (dist > distance_limit || row >= (int)self->height || row < 0 || col >= (int)self->width || col < 0) continue;
|
||||
const int offset = row * self->width + col;
|
||||
double alpha = 1.0 - (dist / half_thickness);
|
||||
uint8_t old_alpha = self->mask[offset];
|
||||
@@ -743,7 +726,7 @@ draw_parametrized_curve_with_derivative(
|
||||
double dx = x_prime(curve_data, t), dy = y_prime(curve_data, t);
|
||||
double d = sqrt(dx * dx + dy * dy);
|
||||
step = 1.0 / fmax(1e-6, d);
|
||||
step = fmax(min_step, fmin(step, 0.01));
|
||||
step = fmax(min_step, fmin(step, max_step));
|
||||
t = fmin(t + step, 1.0);
|
||||
}
|
||||
}
|
||||
@@ -753,8 +736,10 @@ rounded_separator(Canvas *self, uint level, bool left) {
|
||||
uint gap = thickness(self, level, true);
|
||||
int c1x = find_bezier_for_D(minus(self->width, gap), self->height);
|
||||
CubicBezier cb = {.end={.y=self->height - 1}, .c1={.x=c1x}, .c2={.x=c1x, .y=self->height - 1}};
|
||||
if (left) { draw_parametrized_curve(self, level, bezier_x(cb, t), bezier_y(cb, t)); }
|
||||
else { mirror_horizontally(draw_parametrized_curve(self, level, bezier_x(cb, t), bezier_y(cb, t))); }
|
||||
double line_width = thickness_as_float(self, level, true);
|
||||
#define d draw_parametrized_curve_with_derivative(self, &cb, line_width, bezier_x, bezier_y, bezier_prime_x, bezier_prime_y, 0, 0, 0)
|
||||
if (left) { d; } else { mirror_horizontally(d); }
|
||||
#undef d
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -772,56 +757,61 @@ corner_triangle(Canvas *self, const Corner corner) {
|
||||
}
|
||||
|
||||
typedef struct Circle {
|
||||
Point origin;
|
||||
double radius;
|
||||
|
||||
double x, y, radius;
|
||||
double start, end, amt;
|
||||
} Circle;
|
||||
|
||||
static Circle
|
||||
circle(Point origin, double radius, double start_at, double end_at) {
|
||||
circle(double x, double y, double radius, double start_at, double end_at) {
|
||||
double conv = M_PI / 180.;
|
||||
Circle ans = {.origin=origin, .radius=radius, .start=start_at*conv, .end=end_at*conv};
|
||||
Circle ans = {.x=x, .y=y, .radius=radius, .start=start_at*conv, .end=end_at*conv};
|
||||
ans.amt = ans.end - ans.start;
|
||||
return ans;
|
||||
}
|
||||
|
||||
static double
|
||||
circle_x(Circle c, double t) { return c.origin.x + c.radius * cos(c.start + c.amt * t); }
|
||||
static double
|
||||
circle_y(Circle c, double t) { return c.origin.y + c.radius * sin(c.start + c.amt * t); }
|
||||
static double circle_x(const void *v, double t) { const Circle *c=v; return c->x + c->radius * cos(c->start + c->amt * t); }
|
||||
static double circle_y(const void *v, double t) { const Circle *c=v; return c->y + c->radius * sin(c->start + c->amt * t); }
|
||||
static double circle_prime_x(const void *v, double t) { const Circle *c=v; return -c->radius * sin(c->start + c->amt * t); }
|
||||
static double circle_prime_y(const void *v, double t) { const Circle *c=v; return c->radius * cos(c->start + c->amt * t); }
|
||||
|
||||
static void
|
||||
spinner(Canvas *self, uint level, double start_degrees, double end_degrees) {
|
||||
uint w = self->width / 2, h = self->height / 2;
|
||||
uint radius = minus(min(w, h), thickness(self, level, true) / 2);
|
||||
Circle c = circle((Point){.x=w, .y=h}, radius, start_degrees, end_degrees);
|
||||
draw_parametrized_curve(self, level, circle_x(c, t), circle_y(c, t));
|
||||
double x = self->width / 2.0, y = self->height / 2.0;
|
||||
double line_width = thickness_as_float(self, level, true);
|
||||
double radius = fmax(0, fmin(x, y) - line_width / 2.0);
|
||||
Circle c = circle(x, y, radius, start_degrees, end_degrees);
|
||||
draw_parametrized_curve_with_derivative(self, &c, line_width, circle_x, circle_y, circle_prime_x, circle_prime_y, 0, 0, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
draw_circle(Canvas *self, double scale, double gap, bool invert) {
|
||||
const uint w = self->width / 2, h = self->height / 2;
|
||||
const double radius = (int)(scale * min(w, h) - gap / 2);
|
||||
const uint8_t fill = invert ? 0 : 255;
|
||||
fill_circle_of_radius(Canvas *self, double origin_x, double origin_y, double radius, uint8_t alpha) {
|
||||
const double limit = radius * radius;
|
||||
for (uint y = 0; y < self->height; y++) {
|
||||
for (uint x = 0; x < self->width; x++) {
|
||||
double xw = (double)x - w, yh = (double)y - h;
|
||||
if (xw * xw + yh * yh <= limit) self->mask[y * self->width + x] = fill;
|
||||
double xw = (double)x - origin_x, yh = (double)y - origin_y;
|
||||
if (xw * xw + yh * yh <= limit) self->mask[y * self->width + x] = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
draw_fish_eye(Canvas *self, uint level) {
|
||||
uint w = self->width / 2, h = self->height / 2;
|
||||
uint line_width = thickness(self, level, true) / 2;
|
||||
uint radius = minus(min(w, h), line_width);
|
||||
Circle c = circle((Point){.x=w, .y=h}, radius, 0, 360);
|
||||
draw_parametrized_curve(self, level, circle_x(c, t), circle_y(c, t));
|
||||
uint gap = minus(radius, radius / 10);
|
||||
draw_circle(self, 1.0, gap, false);
|
||||
fill_circle(Canvas *self, double scale, double gap, bool invert) {
|
||||
const uint w = self->width / 2, h = self->height / 2;
|
||||
const double radius = (int)(scale * min(w, h) - gap / 2);
|
||||
const uint8_t fill = invert ? 0 : 255;
|
||||
fill_circle_of_radius(self, w, h, radius, fill);
|
||||
}
|
||||
|
||||
static void
|
||||
draw_fish_eye(Canvas *self, uint level UNUSED) {
|
||||
double x = self->width / 2., y = self->height / 2.;
|
||||
double radius = fmin(x, y);
|
||||
double central_radius = (2./3.) * radius;
|
||||
fill_circle_of_radius(self, x, y, central_radius, 255);
|
||||
double line_width = fmax(1. * self->supersample_factor, (radius - central_radius) / 2.5);
|
||||
radius = fmax(0, fmin(x, y) - line_width / 2.);
|
||||
Circle c = circle(x, y, radius, 0, 360);
|
||||
draw_parametrized_curve_with_derivative(self, &c, line_width, circle_x, circle_y, circle_prime_x, circle_prime_y, 0, 0, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1242,28 +1232,28 @@ typedef struct Rectircle {
|
||||
} Rectircle;
|
||||
|
||||
static double
|
||||
rectircle_x(void *v, double t) {
|
||||
Rectircle *r = v;
|
||||
rectircle_x(const void *v, double t) {
|
||||
const Rectircle *r = v;
|
||||
return r->x_start + r->x_sign * r->a * pow(cos(t * (M_PI / 2.0)), r->xexp);
|
||||
}
|
||||
|
||||
static double
|
||||
rectircle_x_prime(void *v, double t) {
|
||||
Rectircle *r = v;
|
||||
rectircle_x_prime(const void *v, double t) {
|
||||
const Rectircle *r = v;
|
||||
t *= (M_PI / 2.0);
|
||||
return r->x_prime_coeff * pow(cos(t), r->x_prime_exp) * sin(t);
|
||||
}
|
||||
|
||||
static double
|
||||
rectircle_y_prime(void *v, double t) {
|
||||
Rectircle *r = v;
|
||||
rectircle_y_prime(const void *v, double t) {
|
||||
const Rectircle *r = v;
|
||||
t *= (M_PI / 2.0);
|
||||
return r->y_prime_coeff * pow(sin(t), r->y_prime_exp) * cos(t);
|
||||
}
|
||||
|
||||
static double
|
||||
rectircle_y(void *v, double t) {
|
||||
Rectircle *r = v;
|
||||
rectircle_y(const void *v, double t) {
|
||||
const Rectircle *r = v;
|
||||
return r->y_start + r->y_sign * r->b * pow(sin(t * (M_PI / 2.0)), r->yexp);
|
||||
}
|
||||
|
||||
@@ -1307,8 +1297,12 @@ rectcircle(Canvas *self, Corner which) {
|
||||
static void
|
||||
rounded_corner(Canvas *self, uint level, Corner which) {
|
||||
Rectircle r = rectcircle(self, which);
|
||||
int x_offset = -((self->width + 1) & 1); // line up with box drawing lines when width is even
|
||||
draw_parametrized_curve_with_derivative(self, &r, level, rectircle_x, rectircle_y, rectircle_x_prime, rectircle_y_prime, x_offset, 0.1);
|
||||
uint cell_width_is_odd = (self->width / self->supersample_factor) & 1;
|
||||
uint cell_height_is_odd = (self->height / self->supersample_factor) & 1;
|
||||
// adjust for odd cell dimensions to line up with box drawing lines
|
||||
int x_offset = -(cell_width_is_odd & 1), y_offset = -(cell_height_is_odd & 1);
|
||||
double line_width = thickness_as_float(self, level, true);
|
||||
draw_parametrized_curve_with_derivative(self, &r, line_width, rectircle_x, rectircle_y, rectircle_x_prime, rectircle_y_prime, x_offset, y_offset, 0.1);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1319,8 +1313,8 @@ commit(Canvas *self, Edge lines, bool solid) {
|
||||
if (lines & LEFT_EDGE) draw_hline(self, 0, hw, hh, level);
|
||||
if (lines & TOP_EDGE) draw_vline(self, 0, hh, hw, level);
|
||||
if (lines & BOTTOM_EDGE) draw_vline(self, hh, self->height, hw, level);
|
||||
draw_circle(self, scale, 0, false);
|
||||
if (!solid) draw_circle(self, scale, thickness(self, level, true), true);
|
||||
fill_circle(self, scale, 0, false);
|
||||
if (!solid) fill_circle(self, scale, thickness(self, level, true), true);
|
||||
}
|
||||
|
||||
// thin and fat line levels
|
||||
@@ -1577,7 +1571,7 @@ START_ALLOW_CASE_RANGE
|
||||
S(L'◟', spinner, 1, 450, 540);
|
||||
S(L'◠', spinner, 1, 180, 360);
|
||||
S(L'◡', spinner, 1, 0, 180);
|
||||
S(L'●', draw_circle, 1.0, 0, false);
|
||||
S(L'●', fill_circle, 1.0, 0, false);
|
||||
S(L'◉', draw_fish_eye, 0);
|
||||
|
||||
C(L'═', dhline, 1, TOP_EDGE | BOTTOM_EDGE);
|
||||
|
||||
@@ -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:
|
||||
|
||||
17
shell.nix
17
shell.nix
@@ -15,6 +15,7 @@ in
|
||||
xxHash
|
||||
simde
|
||||
go_1_23
|
||||
matplotlib
|
||||
]
|
||||
++ optionals stdenv.isDarwin [
|
||||
Cocoa
|
||||
@@ -25,7 +26,6 @@ in
|
||||
OpenGL
|
||||
UniformTypeIdentifiers
|
||||
libpng
|
||||
python3
|
||||
zlib
|
||||
]
|
||||
++ lib.optionals (stdenv.isDarwin && (builtins.hasAttr "UserNotifications" darwin.apple_sdk.frameworks)) [
|
||||
@@ -46,6 +46,10 @@ in
|
||||
wayland
|
||||
openssl
|
||||
dbus
|
||||
cairo #
|
||||
]
|
||||
++ lib.optionals stdenv.hostPlatform.isLinux [
|
||||
wayland-scanner
|
||||
]
|
||||
++ checkInputs;
|
||||
|
||||
@@ -77,10 +81,21 @@ in
|
||||
if stdenv.isDarwin
|
||||
then ''
|
||||
export KITTY_NO_LTO=
|
||||
# Add fonts by hand
|
||||
|
||||
if [ ! -e ./fonts/SymbolsNerdFontMono-Regular.ttf ]; then
|
||||
cp "${nerd-fonts.symbols-only}/share/fonts/truetype/NerdFonts/Symbols/SymbolsNerdFontMono-Regular.ttf" ./fonts/
|
||||
fi
|
||||
''
|
||||
else ''
|
||||
export KITTY_EGL_LIBRARY='${lib.getLib libGL}/lib/libEGL.so.1'
|
||||
export KITTY_STARTUP_NOTIFICATION_LIBRARY='${libstartup_notification}/lib/libstartup-notification-1.so'
|
||||
export KITTY_CANBERRA_LIBRARY='${libcanberra}/lib/libcanberra.so'
|
||||
export KITTY_FONTCONFIG_LIBRARY='${fontconfig.lib}/lib/libfontconfig.so'
|
||||
|
||||
# Add fonts by hand
|
||||
if [ ! -e ./fonts/SymbolsNerdFontMono-Regular.ttf ]; then
|
||||
cp "${nerd-fonts.symbols-only}/share/fonts/truetype/NerdFonts/Symbols/SymbolsNerdFontMono-Regular.ttf" ./fonts/
|
||||
fi
|
||||
'';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user