mirror of
https://github.com/tmux/tmux.git
synced 2026-10-11 04:44:25 +00:00
Match Kitty image sizing and layer order, preserve trailing backgrounds, and add regression coverage.
This commit is contained in:
221
image-kitty.c
221
image-kitty.c
@@ -128,6 +128,8 @@ struct kitty_context {
|
|||||||
struct kitty_image_cache {
|
struct kitty_image_cache {
|
||||||
u_int server_id;
|
u_int server_id;
|
||||||
u_int kitty_id;
|
u_int kitty_id;
|
||||||
|
/* Source creation order, independent of terminal upload order. */
|
||||||
|
u_int order_id;
|
||||||
u_int xpixel;
|
u_int xpixel;
|
||||||
u_int ypixel;
|
u_int ypixel;
|
||||||
struct kitty_placement_cache *placements;
|
struct kitty_placement_cache *placements;
|
||||||
@@ -144,16 +146,32 @@ struct kitty_placement_cache {
|
|||||||
u_int height;
|
u_int height;
|
||||||
u_int source_x;
|
u_int source_x;
|
||||||
u_int source_y;
|
u_int source_y;
|
||||||
int32_t z;
|
int64_t z; /* Logical layer. */
|
||||||
|
int32_t draw_z; /* Terminal layer. */
|
||||||
/* Delete after replacement placements have been drawn. */
|
/* Delete after replacement placements have been drawn. */
|
||||||
int pending_delete;
|
int pending_delete;
|
||||||
struct kitty_placement_cache *next;
|
struct kitty_placement_cache *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Cached placements sorted temporarily when their output layers change. */
|
||||||
|
struct kitty_layer {
|
||||||
|
struct kitty_image_cache *entry;
|
||||||
|
struct kitty_placement_cache *placement;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum kitty_layer_band {
|
||||||
|
KITTY_LAYER_NONE,
|
||||||
|
KITTY_LAYER_BELOW_BACKGROUND,
|
||||||
|
KITTY_LAYER_BELOW_TEXT,
|
||||||
|
KITTY_LAYER_SIXEL,
|
||||||
|
KITTY_LAYER_ABOVE_TEXT
|
||||||
|
};
|
||||||
|
|
||||||
/* Image uploads and ID allocation kept for the lifetime of a terminal. */
|
/* Image uploads and ID allocation kept for the lifetime of a terminal. */
|
||||||
struct kitty_output {
|
struct kitty_output {
|
||||||
struct kitty_image_cache *images;
|
struct kitty_image_cache *images;
|
||||||
u_int next_id;
|
u_int next_id;
|
||||||
|
int layers_changed;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Return the Kitty output state for a terminal. */
|
/* Return the Kitty output state for a terminal. */
|
||||||
@@ -196,7 +214,107 @@ kitty_free_placements(struct kitty_image_cache *entry)
|
|||||||
|
|
||||||
static void kitty_place(struct tty *, struct kitty_image_cache *,
|
static void kitty_place(struct tty *, struct kitty_image_cache *,
|
||||||
struct image *, u_int, u_int, u_int, u_int, u_int, u_int,
|
struct image *, u_int, u_int, u_int, u_int, u_int, u_int,
|
||||||
int32_t);
|
int64_t);
|
||||||
|
static void kitty_send_placement(struct tty *, struct kitty_image_cache *,
|
||||||
|
struct image *, struct kitty_placement_cache *);
|
||||||
|
|
||||||
|
/* Compare output placements by logical z-index and source creation order. */
|
||||||
|
static int
|
||||||
|
kitty_layer_cmp(const void *left, const void *right)
|
||||||
|
{
|
||||||
|
const struct kitty_layer *a = left, *b = right;
|
||||||
|
|
||||||
|
if (a->placement->z < b->placement->z)
|
||||||
|
return (-1);
|
||||||
|
if (a->placement->z > b->placement->z)
|
||||||
|
return (1);
|
||||||
|
if (a->entry->order_id < b->entry->order_id)
|
||||||
|
return (-1);
|
||||||
|
if (a->entry->order_id > b->entry->order_id)
|
||||||
|
return (1);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the output band relative to cell backgrounds, text and SIXEL. */
|
||||||
|
static enum kitty_layer_band
|
||||||
|
kitty_layer_band(int64_t z)
|
||||||
|
{
|
||||||
|
if (z < INT32_MIN / 2)
|
||||||
|
return (KITTY_LAYER_BELOW_BACKGROUND);
|
||||||
|
if (z < 0)
|
||||||
|
return (KITTY_LAYER_BELOW_TEXT);
|
||||||
|
if (z == 0)
|
||||||
|
return (KITTY_LAYER_SIXEL);
|
||||||
|
return (KITTY_LAYER_ABOVE_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assign terminal layers without relying on the order images were uploaded. */
|
||||||
|
static void
|
||||||
|
kitty_update_layers(struct tty *tty, struct kitty_output *ko)
|
||||||
|
{
|
||||||
|
struct kitty_image_cache *entry;
|
||||||
|
struct kitty_placement_cache *placement;
|
||||||
|
struct kitty_layer *layers;
|
||||||
|
struct image *im;
|
||||||
|
size_t count = 0, i;
|
||||||
|
enum kitty_layer_band band;
|
||||||
|
enum kitty_layer_band previous_band = KITTY_LAYER_NONE;
|
||||||
|
int32_t draw_z = 0;
|
||||||
|
|
||||||
|
/* Gather surviving placements, including pieces outside the redraw. */
|
||||||
|
for (entry = ko->images; entry != NULL; entry = entry->next) {
|
||||||
|
for (placement = entry->placements; placement != NULL;
|
||||||
|
placement = placement->next)
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
layers = xcalloc(count, sizeof *layers);
|
||||||
|
i = 0;
|
||||||
|
for (entry = ko->images; entry != NULL; entry = entry->next) {
|
||||||
|
for (placement = entry->placements; placement != NULL;
|
||||||
|
placement = placement->next) {
|
||||||
|
layers[i].entry = entry;
|
||||||
|
layers[i++].placement = placement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qsort(layers, count, sizeof *layers, kitty_layer_cmp);
|
||||||
|
|
||||||
|
/* Keep each band on the same side of background cells and text. */
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
entry = layers[i].entry;
|
||||||
|
placement = layers[i].placement;
|
||||||
|
band = kitty_layer_band(placement->z);
|
||||||
|
if (band != previous_band) {
|
||||||
|
switch (band) {
|
||||||
|
case KITTY_LAYER_BELOW_BACKGROUND:
|
||||||
|
draw_z = INT32_MIN;
|
||||||
|
break;
|
||||||
|
case KITTY_LAYER_BELOW_TEXT:
|
||||||
|
draw_z = INT32_MIN / 2;
|
||||||
|
break;
|
||||||
|
case KITTY_LAYER_SIXEL:
|
||||||
|
draw_z = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
draw_z = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
previous_band = band;
|
||||||
|
} else if (band != KITTY_LAYER_SIXEL &&
|
||||||
|
(placement->z != layers[i - 1].placement->z ||
|
||||||
|
entry->order_id != layers[i - 1].entry->order_id)) {
|
||||||
|
draw_z++;
|
||||||
|
}
|
||||||
|
if (placement->draw_z == draw_z)
|
||||||
|
continue;
|
||||||
|
placement->draw_z = draw_z;
|
||||||
|
im = image_find(entry->server_id);
|
||||||
|
if (im != NULL)
|
||||||
|
kitty_send_placement(tty, entry, im, placement);
|
||||||
|
}
|
||||||
|
free(layers);
|
||||||
|
}
|
||||||
|
|
||||||
/* Place one piece of an existing placement again as a new placement. */
|
/* Place one piece of an existing placement again as a new placement. */
|
||||||
static void
|
static void
|
||||||
@@ -271,6 +389,7 @@ kitty_redraw_start(struct tty *tty, u_int x, u_int y, u_int width,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
placement->pending_delete = 1;
|
placement->pending_delete = 1;
|
||||||
|
ko->layers_changed = 1;
|
||||||
|
|
||||||
/* Preserve pieces outside the redraw area. */
|
/* Preserve pieces outside the redraw area. */
|
||||||
kitty_redraw_keep(tty, entry, placement, x, y, width,
|
kitty_redraw_keep(tty, entry, placement, x, y, width,
|
||||||
@@ -304,6 +423,10 @@ kitty_redraw_finish(struct tty *tty)
|
|||||||
free(placement);
|
free(placement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ko->layers_changed) {
|
||||||
|
kitty_update_layers(tty, ko);
|
||||||
|
ko->layers_changed = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free a cached Kitty image. */
|
/* Free a cached Kitty image. */
|
||||||
@@ -359,32 +482,48 @@ kitty_free_stale_images(struct tty *tty)
|
|||||||
else
|
else
|
||||||
previous->next = next;
|
previous->next = next;
|
||||||
kitty_free_entry(tty, entry, 1);
|
kitty_free_entry(tty, entry, 1);
|
||||||
|
ko->layers_changed = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Send the source rectangle and output layer for a cached placement. */
|
||||||
|
static void
|
||||||
|
kitty_send_placement(struct tty *tty, struct kitty_image_cache *entry,
|
||||||
|
struct image *im, struct kitty_placement_cache *placement)
|
||||||
|
{
|
||||||
|
char control[192];
|
||||||
|
u_int px, py, pwidth, pheight, sx, sy;
|
||||||
|
u_int canvas_width, canvas_height;
|
||||||
|
|
||||||
|
image_get_size_in_cells(im, &sx, &sy);
|
||||||
|
image_get_canvas_size(im, &canvas_width, &canvas_height);
|
||||||
|
px = (uint64_t)placement->source_x * canvas_width / sx;
|
||||||
|
py = (uint64_t)placement->source_y * canvas_height / sy;
|
||||||
|
pwidth = ((uint64_t)(placement->source_x + placement->width) *
|
||||||
|
canvas_width + sx - 1) / sx - px;
|
||||||
|
pheight = ((uint64_t)(placement->source_y + placement->height) *
|
||||||
|
canvas_height + sy - 1) / sy - py;
|
||||||
|
|
||||||
|
/* Account for the duplicate-pixel border added by kitty_upload(). */
|
||||||
|
px++;
|
||||||
|
py++;
|
||||||
|
tty_cursor(tty, placement->x, placement->y);
|
||||||
|
xsnprintf(control, sizeof control,
|
||||||
|
"\033_Ga=p,i=%u,p=%u,x=%u,y=%u,w=%u,h=%u,c=%u,r=%u,z=%d,"
|
||||||
|
"C=1,q=2\033\\", entry->kitty_id,
|
||||||
|
placement->id, px, py, pwidth, pheight, placement->width,
|
||||||
|
placement->height, placement->draw_z);
|
||||||
|
tty_puts(tty, control);
|
||||||
|
}
|
||||||
|
|
||||||
/* Place an image rectangle using the Kitty graphics protocol. */
|
/* Place an image rectangle using the Kitty graphics protocol. */
|
||||||
static void
|
static void
|
||||||
kitty_place(struct tty *tty, struct kitty_image_cache *entry,
|
kitty_place(struct tty *tty, struct kitty_image_cache *entry,
|
||||||
struct image *im, u_int source_x, u_int source_y, u_int width,
|
struct image *im, u_int source_x, u_int source_y, u_int width,
|
||||||
u_int height, u_int destination_x, u_int destination_y, int32_t z)
|
u_int height, u_int destination_x, u_int destination_y, int64_t z)
|
||||||
{
|
{
|
||||||
char control[192];
|
|
||||||
u_int px, py, pwidth, pheight, sx, sy;
|
|
||||||
u_int canvas_width, canvas_height;
|
|
||||||
struct kitty_placement_cache *placement;
|
struct kitty_placement_cache *placement;
|
||||||
|
|
||||||
image_get_size_in_cells(im, &sx, &sy);
|
|
||||||
image_get_canvas_size(im, &canvas_width, &canvas_height);
|
|
||||||
px = (uint64_t)source_x * canvas_width / sx;
|
|
||||||
py = (uint64_t)source_y * canvas_height / sy;
|
|
||||||
pwidth = ((uint64_t)(source_x + width) * canvas_width + sx - 1) /
|
|
||||||
sx - px;
|
|
||||||
pheight = ((uint64_t)(source_y + height) * canvas_height + sy - 1) /
|
|
||||||
sy - py;
|
|
||||||
|
|
||||||
/* Account for the duplicate-pixel border added by kitty_upload(). */
|
|
||||||
px++;
|
|
||||||
py++;
|
|
||||||
placement = xcalloc(1, sizeof *placement);
|
placement = xcalloc(1, sizeof *placement);
|
||||||
do {
|
do {
|
||||||
placement->id = ++entry->next_placement;
|
placement->id = ++entry->next_placement;
|
||||||
@@ -397,15 +536,14 @@ kitty_place(struct tty *tty, struct kitty_image_cache *entry,
|
|||||||
placement->source_x = source_x;
|
placement->source_x = source_x;
|
||||||
placement->source_y = source_y;
|
placement->source_y = source_y;
|
||||||
placement->z = z;
|
placement->z = z;
|
||||||
|
if (z > INT32_MAX)
|
||||||
|
placement->draw_z = INT32_MAX;
|
||||||
|
else
|
||||||
|
placement->draw_z = z;
|
||||||
placement->next = entry->placements;
|
placement->next = entry->placements;
|
||||||
entry->placements = placement;
|
entry->placements = placement;
|
||||||
tty_cursor(tty, destination_x, destination_y);
|
kitty_get_output(tty)->layers_changed = 1;
|
||||||
xsnprintf(control, sizeof control,
|
kitty_send_placement(tty, entry, im, placement);
|
||||||
"\033_Ga=p,i=%u,p=%u,x=%u,y=%u,w=%u,h=%u,c=%u,r=%u,z=%d,"
|
|
||||||
"C=1,q=2\033\\", entry->kitty_id,
|
|
||||||
placement->id, px, py, pwidth, pheight, width,
|
|
||||||
height, z);
|
|
||||||
tty_puts(tty, control);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Upload an image to Kitty and return its output cache entry. */
|
/* Upload an image to Kitty and return its output cache entry. */
|
||||||
@@ -457,6 +595,7 @@ kitty_upload(struct tty *tty, struct image *im)
|
|||||||
} while (id == 0);
|
} while (id == 0);
|
||||||
entry->server_id = image_get_id(im);
|
entry->server_id = image_get_id(im);
|
||||||
entry->kitty_id = id;
|
entry->kitty_id = id;
|
||||||
|
entry->order_id = im->order_id;
|
||||||
entry->xpixel = tty->xpixel;
|
entry->xpixel = tty->xpixel;
|
||||||
entry->ypixel = tty->ypixel;
|
entry->ypixel = tty->ypixel;
|
||||||
entry->next_placement = 0;
|
entry->next_placement = 0;
|
||||||
@@ -521,7 +660,7 @@ kitty_draw_rect(struct tty *tty, const struct image_rect *rectangle)
|
|||||||
u_int source_x, source_y;
|
u_int source_x, source_y;
|
||||||
u_int width, height;
|
u_int width, height;
|
||||||
u_int destination_x, destination_y;
|
u_int destination_x, destination_y;
|
||||||
int32_t z;
|
int64_t z;
|
||||||
|
|
||||||
im = image_rect_get_image(rectangle);
|
im = image_rect_get_image(rectangle);
|
||||||
kitty_free_stale_images(tty);
|
kitty_free_stale_images(tty);
|
||||||
@@ -754,6 +893,7 @@ static u_int
|
|||||||
kitty_source_set(struct kitty_context *kc, u_int id, struct image *im)
|
kitty_source_set(struct kitty_context *kc, u_int id, struct image *im)
|
||||||
{
|
{
|
||||||
struct kitty_source *source;
|
struct kitty_source *source;
|
||||||
|
struct image *old;
|
||||||
u_int old_id = 0;
|
u_int old_id = 0;
|
||||||
|
|
||||||
if (id == 0)
|
if (id == 0)
|
||||||
@@ -766,6 +906,9 @@ kitty_source_set(struct kitty_context *kc, u_int id, struct image *im)
|
|||||||
kc->sources = source;
|
kc->sources = source;
|
||||||
} else {
|
} else {
|
||||||
old_id = source->server_id;
|
old_id = source->server_id;
|
||||||
|
old = image_find(old_id);
|
||||||
|
if (old != NULL)
|
||||||
|
im->order_id = old->order_id;
|
||||||
kitty_placements_free(source);
|
kitty_placements_free(source);
|
||||||
image_free(source->server_id);
|
image_free(source->server_id);
|
||||||
}
|
}
|
||||||
@@ -1035,10 +1178,13 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
|
|||||||
u_int cell_width, cell_height, source_width, source_height;
|
u_int cell_width, cell_height, source_width, source_height;
|
||||||
u_int x_offset, y_offset;
|
u_int x_offset, y_offset;
|
||||||
uint64_t canvas_width, canvas_height;
|
uint64_t canvas_width, canvas_height;
|
||||||
double width_scale, height_scale, scale = 1, units = 1;
|
double width_scale = 1, height_scale = 1;
|
||||||
|
double scale = 1, units = 1;
|
||||||
double value;
|
double value;
|
||||||
int natural_size = 0;
|
int natural_size = 0;
|
||||||
|
int stretch;
|
||||||
|
|
||||||
|
stretch = !ks->virtual && ks->columns != 0 && ks->rows != 0;
|
||||||
image_get_size(source, &source_width, &source_height);
|
image_get_size(source, &source_width, &source_height);
|
||||||
view.x = ks->source_x;
|
view.x = ks->source_x;
|
||||||
view.y = ks->source_y;
|
view.y = ks->source_y;
|
||||||
@@ -1053,7 +1199,7 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
|
|||||||
if (ks->source_height != 0 && view.height > ks->source_height)
|
if (ks->source_height != 0 && view.height > ks->source_height)
|
||||||
view.height = ks->source_height;
|
view.height = ks->source_height;
|
||||||
|
|
||||||
/* Choose placement dimensions in cells, preserving the aspect ratio. */
|
/* Preserve aspect ratio when computing an automatic cell dimension. */
|
||||||
cell_width = (xpixel == 0 ? 8 : xpixel);
|
cell_width = (xpixel == 0 ? 8 : xpixel);
|
||||||
cell_height = (ypixel == 0 ? 16 : ypixel);
|
cell_height = (ypixel == 0 ? 16 : ypixel);
|
||||||
x_offset = ks->x_offset;
|
x_offset = ks->x_offset;
|
||||||
@@ -1080,6 +1226,7 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
|
|||||||
&view.sx, &view.sy);
|
&view.sx, &view.sy);
|
||||||
} else if (view.sy == 0) {
|
} else if (view.sy == 0) {
|
||||||
scale = ((double)view.sx * cell_width - x_offset) / view.width;
|
scale = ((double)view.sx * cell_width - x_offset) / view.width;
|
||||||
|
width_scale = height_scale = scale;
|
||||||
value = ceil((view.height * scale + y_offset) / cell_height);
|
value = ceil((view.height * scale + y_offset) / cell_height);
|
||||||
if (value > USHRT_MAX)
|
if (value > USHRT_MAX)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
@@ -1087,6 +1234,7 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
|
|||||||
} else if (view.sx == 0) {
|
} else if (view.sx == 0) {
|
||||||
scale = ((double)view.sy * cell_height - y_offset) /
|
scale = ((double)view.sy * cell_height - y_offset) /
|
||||||
view.height;
|
view.height;
|
||||||
|
width_scale = height_scale = scale;
|
||||||
value = ceil((view.width * scale + x_offset) / cell_width);
|
value = ceil((view.width * scale + x_offset) / cell_width);
|
||||||
if (value > USHRT_MAX)
|
if (value > USHRT_MAX)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
@@ -1099,6 +1247,10 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
|
|||||||
scale = width_scale;
|
scale = width_scale;
|
||||||
if (height_scale < scale)
|
if (height_scale < scale)
|
||||||
scale = height_scale;
|
scale = height_scale;
|
||||||
|
|
||||||
|
/* Only Unicode placeholders preserve aspect ratio here. */
|
||||||
|
if (ks->virtual)
|
||||||
|
width_scale = height_scale = scale;
|
||||||
}
|
}
|
||||||
canvas_width = (uint64_t)view.sx * cell_width;
|
canvas_width = (uint64_t)view.sx * cell_width;
|
||||||
canvas_height = (uint64_t)view.sy * cell_height;
|
canvas_height = (uint64_t)view.sy * cell_height;
|
||||||
@@ -1114,10 +1266,10 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
|
|||||||
if (value > UINT_MAX)
|
if (value > UINT_MAX)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
view.canvas_height = value;
|
view.canvas_height = value;
|
||||||
view.scaled_width = floor(view.width * scale * units);
|
view.scaled_width = floor(view.width * width_scale * units);
|
||||||
if (view.scaled_width < view.width)
|
if (view.scaled_width < view.width)
|
||||||
view.scaled_width = view.width;
|
view.scaled_width = view.width;
|
||||||
view.scaled_height = floor(view.height * scale * units);
|
view.scaled_height = floor(view.height * height_scale * units);
|
||||||
if (view.scaled_height < view.height)
|
if (view.scaled_height < view.height)
|
||||||
view.scaled_height = view.height;
|
view.scaled_height = view.height;
|
||||||
view.x_offset = floor(x_offset * units);
|
view.x_offset = floor(x_offset * units);
|
||||||
@@ -1129,17 +1281,18 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
|
|||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Centre the image when both placement dimensions were specified. */
|
/* Centre Unicode-placeholder images within their rectangle. */
|
||||||
if (ks->columns != 0 && ks->rows != 0) {
|
if (ks->virtual && ks->columns != 0 && ks->rows != 0) {
|
||||||
view.x_offset += (view.canvas_width - view.x_offset -
|
view.x_offset += (view.canvas_width - view.x_offset -
|
||||||
view.scaled_width) / 2;
|
view.scaled_width) / 2;
|
||||||
view.y_offset += (view.canvas_height - view.y_offset -
|
view.y_offset += (view.canvas_height - view.y_offset -
|
||||||
view.scaled_height) / 2;
|
view.scaled_height) / 2;
|
||||||
}
|
}
|
||||||
/* A matching rectangle needs neither resampling nor padding. */
|
/* Let the output terminal scale unpadded rectangular placements. */
|
||||||
if (x_offset == 0 && y_offset == 0 &&
|
if (x_offset == 0 && y_offset == 0 &&
|
||||||
|
(stretch ||
|
||||||
(uint64_t)view.width * canvas_height ==
|
(uint64_t)view.width * canvas_height ==
|
||||||
(uint64_t)view.height * canvas_width) {
|
(uint64_t)view.height * canvas_width)) {
|
||||||
view.canvas_width = view.width;
|
view.canvas_width = view.width;
|
||||||
view.scaled_width = view.width;
|
view.scaled_width = view.width;
|
||||||
view.canvas_height = view.height;
|
view.canvas_height = view.height;
|
||||||
|
|||||||
14
image.c
14
image.c
@@ -34,7 +34,7 @@
|
|||||||
/* A cell-aligned part of an image to draw at a terminal position. */
|
/* A cell-aligned part of an image to draw at a terminal position. */
|
||||||
struct image_rect {
|
struct image_rect {
|
||||||
struct image *image;
|
struct image *image;
|
||||||
int32_t z;
|
int64_t z;
|
||||||
u_int source_x; /* Origin in image cells. */
|
u_int source_x; /* Origin in image cells. */
|
||||||
u_int source_y;
|
u_int source_y;
|
||||||
u_int sx; /* Size in cells. */
|
u_int sx; /* Size in cells. */
|
||||||
@@ -270,8 +270,8 @@ image_placement_cmp(const struct image_placement *a,
|
|||||||
if (a->input == IMAGE_INPUT_KITTY && a->z != b->z)
|
if (a->input == IMAGE_INPUT_KITTY && a->z != b->z)
|
||||||
return (a->z < b->z ? -1 : 1);
|
return (a->z < b->z ? -1 : 1);
|
||||||
if (a->input == IMAGE_INPUT_KITTY &&
|
if (a->input == IMAGE_INPUT_KITTY &&
|
||||||
a->app_image_id != b->app_image_id) {
|
a->image->order_id != b->image->order_id) {
|
||||||
return (a->app_image_id < b->app_image_id ? -1 : 1);
|
return (a->image->order_id < b->image->order_id ? -1 : 1);
|
||||||
}
|
}
|
||||||
if (a->serial != b->serial)
|
if (a->serial != b->serial)
|
||||||
return (a->serial < b->serial ? -1 : 1);
|
return (a->serial < b->serial ? -1 : 1);
|
||||||
@@ -940,7 +940,7 @@ image_rect_get_coords(const struct image_rect *rectangle,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Return the output z-index for a drawing rectangle. */
|
/* Return the output z-index for a drawing rectangle. */
|
||||||
int32_t
|
int64_t
|
||||||
image_rect_get_z(const struct image_rect *rectangle)
|
image_rect_get_z(const struct image_rect *rectangle)
|
||||||
{
|
{
|
||||||
return (rectangle->z);
|
return (rectangle->z);
|
||||||
@@ -962,6 +962,7 @@ image_alloc(u_int width, u_int height, u_int canvas_width,
|
|||||||
|
|
||||||
im->references = 1;
|
im->references = 1;
|
||||||
im->source_id = im->id;
|
im->source_id = im->id;
|
||||||
|
im->order_id = im->id;
|
||||||
im->width = width;
|
im->width = width;
|
||||||
im->height = height;
|
im->height = height;
|
||||||
im->canvas_width = canvas_width;
|
im->canvas_width = canvas_width;
|
||||||
@@ -1096,6 +1097,7 @@ image_create_view(struct image *source, const struct image_view *view)
|
|||||||
/* Retain the source for both shared pixels and placement identity. */
|
/* Retain the source for both shared pixels and placement identity. */
|
||||||
im->parent_id = source->id;
|
im->parent_id = source->id;
|
||||||
im->source_id = source->source_id;
|
im->source_id = source->source_id;
|
||||||
|
im->order_id = source->order_id;
|
||||||
image_ref(source->id);
|
image_ref(source->id);
|
||||||
return (im);
|
return (im);
|
||||||
}
|
}
|
||||||
@@ -1542,8 +1544,8 @@ image_draw_span(const struct image_backend *backend, struct tty *tty,
|
|||||||
rectangle.image = placement->image;
|
rectangle.image = placement->image;
|
||||||
if (placement->input == IMAGE_INPUT_SIXEL)
|
if (placement->input == IMAGE_INPUT_SIXEL)
|
||||||
rectangle.z = 0;
|
rectangle.z = 0;
|
||||||
else if (placement->z >= 0 && placement->z < INT32_MAX)
|
else if (placement->z >= 0)
|
||||||
rectangle.z = placement->z + 1;
|
rectangle.z = (int64_t)placement->z + 1;
|
||||||
else
|
else
|
||||||
rectangle.z = placement->z;
|
rectangle.z = placement->z;
|
||||||
rectangle.source_x = span->source_x + start - span->x;
|
rectangle.source_x = span->source_x + start - span->x;
|
||||||
|
|||||||
136
regress/image-kitty-layers.sh
Normal file
136
regress/image-kitty-layers.sh
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Kitty output layers must not depend on upload, placement or redraw order.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
export TERM
|
||||||
|
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
export TEST_TMUX
|
||||||
|
command -v python3 >/dev/null || exit 0
|
||||||
|
|
||||||
|
python3 - <<'PY'
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
|
||||||
|
binary = str(Path(os.environ['TEST_TMUX']).resolve())
|
||||||
|
inner = [binary, '-Lkitty-layers-inner' + str(os.getpid()), '-f/dev/null']
|
||||||
|
outer = [binary, '-Lkitty-layers-outer' + str(os.getpid()), '-f/dev/null']
|
||||||
|
|
||||||
|
|
||||||
|
# Run one command against the selected test server.
|
||||||
|
def run(server, *args):
|
||||||
|
result = subprocess.check_output(server + list(args), text=True).strip()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Build a graphics command that leaves the cursor unchanged.
|
||||||
|
def graphics(control, payload=''):
|
||||||
|
return '\033_G' + control + (';' + payload if payload else '') + '\033\\'
|
||||||
|
|
||||||
|
|
||||||
|
# Replay uploads and placements using Kitty's internal-ID tie breaker.
|
||||||
|
def scene(data):
|
||||||
|
images, placements = {}, {}
|
||||||
|
loading = bytearray()
|
||||||
|
upload = None
|
||||||
|
for match in re.finditer(rb'\033_G([^\033]*)\033\\', data):
|
||||||
|
control, _, payload = match[1].partition(b';')
|
||||||
|
fields = dict(item.split(b'=', 1) for item in control.split(b','))
|
||||||
|
action = fields.get(b'a', b't')
|
||||||
|
image_id = int(fields.get(b'i', 0))
|
||||||
|
if action == b't':
|
||||||
|
if image_id:
|
||||||
|
upload = image_id
|
||||||
|
loading.clear()
|
||||||
|
loading.extend(base64.b64decode(payload))
|
||||||
|
if fields.get(b'm', b'0') == b'0':
|
||||||
|
# The one-pixel fixture has a duplicated border on each side.
|
||||||
|
assert len(loading) == 36, len(loading)
|
||||||
|
images[upload] = (len(images), bytes(loading[16:20]))
|
||||||
|
elif action == b'p':
|
||||||
|
placements[image_id, int(fields[b'p'])] = int(fields.get(b'z', 0))
|
||||||
|
elif action == b'd':
|
||||||
|
if b'p' in fields:
|
||||||
|
placements.pop((image_id, int(fields[b'p'])), None)
|
||||||
|
else:
|
||||||
|
placements = {key: z for key, z in placements.items()
|
||||||
|
if key[0] != image_id}
|
||||||
|
if not placements:
|
||||||
|
return None, len(images)
|
||||||
|
top = max(placements, key=lambda key: (placements[key], images[key[0]][0]))
|
||||||
|
return images[top[0]][1], len(images)
|
||||||
|
|
||||||
|
|
||||||
|
# Wait for a rendered scene, rather than only the application's input write.
|
||||||
|
def expect(name, colour, uploads):
|
||||||
|
deadline = time.monotonic() + 3
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
actual, count = scene(capture.read_bytes())
|
||||||
|
if actual == colour and count >= uploads:
|
||||||
|
return
|
||||||
|
time.sleep(0.05)
|
||||||
|
raise AssertionError((name, actual, colour, count, uploads))
|
||||||
|
|
||||||
|
|
||||||
|
red = bytes((255, 0, 0, 255))
|
||||||
|
white = bytes((255, 255, 255, 255))
|
||||||
|
older = graphics('a=t,q=2,i=200,f=32,s=1,v=1', base64.b64encode(red).decode())
|
||||||
|
newer = graphics('a=t,q=2,i=100,f=32,s=1,v=1', base64.b64encode(white).decode())
|
||||||
|
put_older = graphics('a=p,q=2,C=1,i=200,c=1,r=1')
|
||||||
|
put_newer = graphics('a=p,q=2,C=1,i=100,c=1,r=1')
|
||||||
|
commands = [older + newer + put_newer, put_older,
|
||||||
|
older + put_older,
|
||||||
|
graphics('a=d,d=I,q=2,i=200') + older + put_older,
|
||||||
|
put_older.replace('C=1', 'C=1,z=2147483646') +
|
||||||
|
put_newer.replace('C=1', 'C=1,z=2147483647')]
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory(prefix='tmux-kitty-layers-') as tmp:
|
||||||
|
directory = Path(tmp)
|
||||||
|
capture = directory / 'output'
|
||||||
|
capture.touch()
|
||||||
|
helper = directory / 'emit.py'
|
||||||
|
helper.write_text('import os, sys, time\n'
|
||||||
|
'commands = ' + repr(commands) + '\n'
|
||||||
|
'for index, command in enumerate(commands):\n'
|
||||||
|
' if index: sys.stdin.readline()\n'
|
||||||
|
' os.write(1, command.encode())\n'
|
||||||
|
'time.sleep(30)\n')
|
||||||
|
try:
|
||||||
|
run(inner, 'new-session', '-d', '-x', '40', '-y', '12',
|
||||||
|
shlex.join(['python3', str(helper)]))
|
||||||
|
if run(inner, 'display-message', '-p', '#{image_support}') == '0':
|
||||||
|
raise SystemExit(0)
|
||||||
|
run(inner, 'set', '-g', 'status', 'off')
|
||||||
|
run(inner, 'set', '-as', 'terminal-features', ',*:kitty')
|
||||||
|
pane = run(outer, 'new-session', '-dP', '-F', '#{pane_id}',
|
||||||
|
'-x', '40', '-y', '12', 'exec sh')
|
||||||
|
run(outer, 'set', '-g', 'status', 'off')
|
||||||
|
run(outer, 'pipe-pane', '-t', pane, '-O',
|
||||||
|
'cat >' + shlex.quote(str(capture)))
|
||||||
|
run(outer, 'send-keys', '-t', pane, '-l', shlex.join(inner + ['attach']))
|
||||||
|
run(outer, 'send-keys', '-t', pane, 'Enter')
|
||||||
|
expect('newer-placed-first', white, 1)
|
||||||
|
run(inner, 'send-keys', 'Enter')
|
||||||
|
expect('older-uploaded-last', white, 2)
|
||||||
|
run(inner, 'refresh-client')
|
||||||
|
time.sleep(0.2)
|
||||||
|
expect('full-redraw', white, 2)
|
||||||
|
run(inner, 'send-keys', 'Enter')
|
||||||
|
expect('older-retransmitted', white, 3)
|
||||||
|
run(inner, 'send-keys', 'Enter')
|
||||||
|
expect('older-recreated', red, 4)
|
||||||
|
run(inner, 'send-keys', 'Enter')
|
||||||
|
expect('maximum-z-order', white, 6)
|
||||||
|
finally:
|
||||||
|
for server in (outer, inner):
|
||||||
|
subprocess.run(server + ['kill-server'], stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL)
|
||||||
|
PY
|
||||||
@@ -327,14 +327,52 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
|||||||
check('placeholder-erase', virtual + colours + ph + '\033[H\033[2K', '0,0',
|
check('placeholder-erase', virtual + colours + ph + '\033[H\033[2K', '0,0',
|
||||||
text='', render=r'^\s*$')
|
text='', render=r'^\s*$')
|
||||||
white_pixel = '/////w=='
|
white_pixel = '/////w=='
|
||||||
check('letterbox', graphics('a=T,q=2,f=32,s=1,v=1,c=2,r=2,C=1', white_pixel),
|
# Ordinary placements stretch; placeholders retain aspect ratio.
|
||||||
'0,0', render='▄▄')
|
check('stretch-tall', graphics('a=T,q=2,f=32,s=1,v=1,c=2,r=2,C=1',
|
||||||
check('pillarbox', graphics('a=T,q=2,f=32,s=1,v=1,c=4,r=1,C=1', white_pixel),
|
white_pixel),
|
||||||
'0,0', render=(r'^(?:\x1b\[[0-9;]+m)* '
|
'0,0', render=r'48;2;255;255;255m {2}')
|
||||||
r'(?:\x1b\[[0-9;]+m)*\x1b\[48;2;255;255;255m {2}'))
|
check('stretch-wide', graphics('a=T,q=2,f=32,s=1,v=1,c=4,r=1,C=1',
|
||||||
|
white_pixel),
|
||||||
|
'0,0', render=r'48;2;255;255;255m {4}')
|
||||||
|
check('stretch-crop', graphics('a=T,q=2,f=32,s=24,v=32,x=4,y=8,w=8,h=8,'
|
||||||
|
'c=2,r=2,C=1', white),
|
||||||
|
'0,0', render=r'48;2;255;255;255m {2}')
|
||||||
|
check('virtual-letterbox', graphics('a=T,q=2,U=1,i=7,p=1,f=32,s=1,v=1,'
|
||||||
|
'c=2,r=2', white_pixel) + colours +
|
||||||
|
ph + '\u0305\u0305' + ph + '\u0305\u030d',
|
||||||
|
'2,0', render='▄▄')
|
||||||
|
check('virtual-pillarbox', graphics('a=T,q=2,U=1,i=7,p=1,f=32,s=1,v=1,'
|
||||||
|
'c=4,r=1', white_pixel) + colours +
|
||||||
|
ph + '\u0305\u0305' + ph + '\u0305\u030d',
|
||||||
|
'2,0', render=(r'^(?:\x1b\[[0-9;]+m)* '
|
||||||
|
r'(?:\x1b\[[0-9;]+m)*\x1b\[48;2;255;255;255m '))
|
||||||
|
# Equal-z images follow source creation order, even with descending IDs.
|
||||||
|
older = graphics('a=t,q=2,i=200,f=32,s=1,v=1', pixel)
|
||||||
|
newer = graphics('a=t,q=2,i=100,f=32,s=1,v=1', white_pixel)
|
||||||
|
put_older = graphics('a=p,q=2,C=1,i=200,c=1,r=1')
|
||||||
|
put_newer = graphics('a=p,q=2,C=1,i=100,c=1,r=1')
|
||||||
|
check('same-z-descending-ids', older + newer + put_older + put_newer,
|
||||||
|
'0,0', render=r'48;2;255;255;255m ')
|
||||||
|
check('same-z-reversed-placements',
|
||||||
|
older + newer + put_newer + put_older,
|
||||||
|
'0,0', render=r'48;2;255;255;255m ')
|
||||||
|
check('same-z-retransmit',
|
||||||
|
older + newer + put_newer + older + put_older,
|
||||||
|
'0,0', render=r'48;2;255;255;255m ')
|
||||||
|
check('same-z-recreate', older + newer + put_newer +
|
||||||
|
graphics('a=d,d=I,q=2,i=200') + older + put_older,
|
||||||
|
'0,0', render=r'48;2;255;0;0m ')
|
||||||
|
# An empty line tail must not discard the preceding background run.
|
||||||
|
background = '\033[11GX\033[17G\033[42m \033[0m'
|
||||||
|
check('background-tail', background, '20,0', render=r'42m {4}')
|
||||||
|
check('background-tail-image',
|
||||||
|
graphics('a=T,q=2,C=1,f=32,s=1,v=1,c=20,r=1,z=-1073741825',
|
||||||
|
white_pixel) +
|
||||||
|
background, '20,0', render=r'42m {4}')
|
||||||
offset_source = base64.b64encode(b'\xff' * (8 * 32 * 4)).decode()
|
offset_source = base64.b64encode(b'\xff' * (8 * 32 * 4)).decode()
|
||||||
check('scaled-offset', graphics('a=T,q=2,f=32,s=8,v=32,c=1,r=1,X=4,C=1',
|
check('scaled-offset', graphics('a=T,q=2,f=32,s=8,v=32,c=1,r=1,X=4,C=1',
|
||||||
offset_source), '0,0', render=r'12[78];12[78];12[78]m')
|
offset_source),
|
||||||
|
'0,0', render=r'191;191;191m')
|
||||||
square = base64.b64encode(b'\xff' * (8 * 8 * 4)).decode()
|
square = base64.b64encode(b'\xff' * (8 * 8 * 4)).decode()
|
||||||
check('vertical-offset', graphics('a=T,q=2,f=32,s=8,v=8,c=1,r=1,Y=8,C=1',
|
check('vertical-offset', graphics('a=T,q=2,f=32,s=8,v=8,c=1,r=1,Y=8,C=1',
|
||||||
square), '0,0', render='▄')
|
square), '0,0', render='▄')
|
||||||
|
|||||||
3
tmux.h
3
tmux.h
@@ -1093,6 +1093,7 @@ struct image {
|
|||||||
/* Retained parent (zero for sources) and original source IDs. */
|
/* Retained parent (zero for sources) and original source IDs. */
|
||||||
u_int parent_id;
|
u_int parent_id;
|
||||||
u_int source_id;
|
u_int source_id;
|
||||||
|
u_int order_id; /* Original creation order. */
|
||||||
/* Pixel dimensions, including the logical transparent canvas. */
|
/* Pixel dimensions, including the logical transparent canvas. */
|
||||||
u_int width;
|
u_int width;
|
||||||
u_int height;
|
u_int height;
|
||||||
@@ -4405,7 +4406,7 @@ int image_get_fallback_at(struct tty *, struct screen *, u_int,
|
|||||||
struct image *image_rect_get_image(const struct image_rect *);
|
struct image *image_rect_get_image(const struct image_rect *);
|
||||||
void image_rect_get_coords(const struct image_rect *,
|
void image_rect_get_coords(const struct image_rect *,
|
||||||
u_int *, u_int *, u_int *, u_int *, u_int *, u_int *);
|
u_int *, u_int *, u_int *, u_int *, u_int *, u_int *);
|
||||||
int32_t image_rect_get_z(const struct image_rect *);
|
int64_t image_rect_get_z(const struct image_rect *);
|
||||||
void image_clear(struct screen_write_ctx *, u_int);
|
void image_clear(struct screen_write_ctx *, u_int);
|
||||||
void image_clear_kitty(struct screen_write_ctx *,
|
void image_clear_kitty(struct screen_write_ctx *,
|
||||||
const struct kitty_parse_result *);
|
const struct kitty_parse_result *);
|
||||||
|
|||||||
@@ -268,6 +268,10 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
|||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
empty = nx - i;
|
empty = nx - i;
|
||||||
|
|
||||||
|
/* Flush the background before clearing the tail. */
|
||||||
|
if (empty != 0 && gcp->bg != last.bg)
|
||||||
|
empty = 0;
|
||||||
} else {
|
} else {
|
||||||
/* Get the current cell. */
|
/* Get the current cell. */
|
||||||
grid_view_get_cell(gd, px + i, py, &gc);
|
grid_view_get_cell(gd, px + i, py, &gc);
|
||||||
|
|||||||
Reference in New Issue
Block a user