mirror of
https://github.com/tmux/tmux.git
synced 2026-10-09 11:44:23 +00:00
Fix Kitty image deletion, placeholders and placement geometry.
This commit is contained in:
6
grid.c
6
grid.c
@@ -739,7 +739,7 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
|
||||
if (nx == 0 || ny == 0)
|
||||
return;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_grid_remove_sixel_spans(gd, px, py, nx, ny);
|
||||
image_grid_remove_overwritten_spans(gd, px, py, nx, ny);
|
||||
#endif
|
||||
|
||||
if (px == 0 && nx == gd->sx) {
|
||||
@@ -793,8 +793,8 @@ grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
|
||||
for (yy = py; yy < py + ny; yy++) {
|
||||
gl = &gd->linedata[yy];
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Remove any SIXEL spans invalidated by clearing this line. */
|
||||
image_grid_remove_sixel_spans(gd, 0, yy, gd->sx, 1);
|
||||
/* Remove SIXEL and placeholder spans on this line. */
|
||||
image_grid_remove_overwritten_spans(gd, 0, yy, gd->sx, 1);
|
||||
/* Preserve the remaining Kitty spans. */
|
||||
images = gl->images;
|
||||
#endif
|
||||
|
||||
691
image-kitty.c
691
image-kitty.c
@@ -18,6 +18,7 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <resolv.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -85,6 +86,7 @@ struct kitty_state {
|
||||
u_int columns;
|
||||
u_int rows;
|
||||
u_int image_id;
|
||||
u_int image_number;
|
||||
u_int placement_id;
|
||||
int32_t z;
|
||||
u_int quiet;
|
||||
@@ -92,6 +94,7 @@ struct kitty_state {
|
||||
int virtual;
|
||||
u_int data_size;
|
||||
int more;
|
||||
int unsupported;
|
||||
|
||||
u_char *raw;
|
||||
size_t rawlen;
|
||||
@@ -101,13 +104,13 @@ struct kitty_placement {
|
||||
u_int placement_id;
|
||||
u_int server_id;
|
||||
int32_t z;
|
||||
int virtual;
|
||||
struct kitty_placement *next;
|
||||
};
|
||||
|
||||
struct kitty_source {
|
||||
u_int app_id;
|
||||
u_int server_id;
|
||||
u_int virtual_id;
|
||||
struct kitty_placement *placements;
|
||||
struct kitty_source *next;
|
||||
};
|
||||
@@ -575,6 +578,7 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len)
|
||||
const u_char *value, *end = buf + len, *comma;
|
||||
size_t valuelen;
|
||||
u_int number;
|
||||
int32_t signed_number;
|
||||
char key;
|
||||
|
||||
while (buf < end) {
|
||||
@@ -606,6 +610,20 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len)
|
||||
case 'o':
|
||||
ks->compression = value[0];
|
||||
break;
|
||||
case 'P': case 'Q':
|
||||
if (kitty_number((const char *)value, valuelen,
|
||||
&number) != 0)
|
||||
return (-1);
|
||||
if (number != 0)
|
||||
ks->unsupported = 1;
|
||||
break;
|
||||
case 'H': case 'V':
|
||||
if (kitty_signed_number((const char *)value, valuelen,
|
||||
&signed_number) != 0)
|
||||
return (-1);
|
||||
if (signed_number != 0)
|
||||
ks->unsupported = 1;
|
||||
break;
|
||||
case 'z':
|
||||
if (kitty_signed_number((const char *)value, valuelen,
|
||||
&ks->z) != 0)
|
||||
@@ -621,6 +639,7 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len)
|
||||
case 'c':
|
||||
case 'r':
|
||||
case 'i':
|
||||
case 'I':
|
||||
case 'p':
|
||||
case 'q':
|
||||
case 'm':
|
||||
@@ -643,6 +662,7 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len)
|
||||
case 'c': ks->columns = number; break;
|
||||
case 'r': ks->rows = number; break;
|
||||
case 'i': ks->image_id = number; break;
|
||||
case 'I': ks->image_number = number; break;
|
||||
case 'p': ks->placement_id = number; break;
|
||||
case 'q': ks->quiet = number; break;
|
||||
case 'm': ks->more = (number != 0); break;
|
||||
@@ -683,34 +703,13 @@ kitty_placements_free(struct kitty_source *source)
|
||||
source->placements = NULL;
|
||||
}
|
||||
|
||||
/* Free placement images belonging to all Kitty source images. */
|
||||
/* Free a source image and its parser-side placements. */
|
||||
static void
|
||||
kitty_placements_free_all(struct kitty_context *kc)
|
||||
kitty_source_free(struct kitty_source *source)
|
||||
{
|
||||
struct kitty_source *source;
|
||||
|
||||
for (source = kc->sources; source != NULL; source = source->next)
|
||||
kitty_placements_free(source);
|
||||
}
|
||||
|
||||
/* Remove all parser-side placements at a Kitty z-index. */
|
||||
static void
|
||||
kitty_placements_remove_z(struct kitty_context *kc, int32_t z)
|
||||
{
|
||||
struct kitty_source *source;
|
||||
struct kitty_placement **pp, *placement;
|
||||
|
||||
for (source = kc->sources; source != NULL; source = source->next) {
|
||||
for (pp = &source->placements; (placement = *pp) != NULL; ) {
|
||||
if (placement->z != z) {
|
||||
pp = &placement->next;
|
||||
continue;
|
||||
}
|
||||
*pp = placement->next;
|
||||
image_free(placement->server_id);
|
||||
free(placement);
|
||||
}
|
||||
}
|
||||
kitty_placements_free(source);
|
||||
image_free(source->server_id);
|
||||
free(source);
|
||||
}
|
||||
|
||||
/* Free Kitty graphics parser state. */
|
||||
@@ -725,11 +724,7 @@ kitty_free_state(void *state)
|
||||
kitty_state_free(kc->transfer);
|
||||
for (source = kc->sources; source != NULL; source = next) {
|
||||
next = source->next;
|
||||
kitty_placements_free(source);
|
||||
if (source->virtual_id != 0)
|
||||
image_free(source->virtual_id);
|
||||
image_free(source->server_id);
|
||||
free(source);
|
||||
kitty_source_free(source);
|
||||
}
|
||||
free(kc);
|
||||
}
|
||||
@@ -765,10 +760,6 @@ kitty_source_set(struct kitty_context *kc, u_int id, struct image *im)
|
||||
} else {
|
||||
old_id = source->server_id;
|
||||
kitty_placements_free(source);
|
||||
if (source->virtual_id != 0) {
|
||||
image_free(source->virtual_id);
|
||||
source->virtual_id = 0;
|
||||
}
|
||||
image_free(source->server_id);
|
||||
}
|
||||
image_ref(image_get_id(im));
|
||||
@@ -778,31 +769,32 @@ kitty_source_set(struct kitty_context *kc, u_int id, struct image *im)
|
||||
|
||||
/* Associate a placement ID with an image. */
|
||||
static u_int
|
||||
kitty_placement_set(struct kitty_context *kc, u_int image_id,
|
||||
u_int placement_id, int32_t z, struct image *im)
|
||||
kitty_placement_set(struct kitty_context *kc, const struct kitty_state *ks,
|
||||
struct image *im)
|
||||
{
|
||||
struct kitty_source *source;
|
||||
struct kitty_placement *placement;
|
||||
u_int old_id;
|
||||
|
||||
if (placement_id == 0)
|
||||
return (0);
|
||||
source = kitty_source_find(kc, image_id);
|
||||
source = kitty_source_find(kc, ks->image_id);
|
||||
if (source == NULL)
|
||||
return (0);
|
||||
for (placement = source->placements; placement != NULL;
|
||||
placement = placement->next) {
|
||||
if (placement->placement_id == placement_id)
|
||||
break;
|
||||
if (ks->placement_id != 0) {
|
||||
if (placement->placement_id == ks->placement_id)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (placement == NULL) {
|
||||
placement = xcalloc(1, sizeof *placement);
|
||||
placement->placement_id = placement_id;
|
||||
placement->placement_id = ks->placement_id;
|
||||
placement->next = source->placements;
|
||||
source->placements = placement;
|
||||
}
|
||||
old_id = placement->server_id;
|
||||
placement->z = z;
|
||||
placement->z = ks->z;
|
||||
placement->virtual = ks->virtual;
|
||||
image_ref(image_get_id(im));
|
||||
placement->server_id = image_get_id(im);
|
||||
if (old_id != 0)
|
||||
@@ -810,72 +802,6 @@ kitty_placement_set(struct kitty_context *kc, u_int image_id,
|
||||
return (old_id);
|
||||
}
|
||||
|
||||
/* Remove and return the image associated with a Kitty placement ID. */
|
||||
static struct image *
|
||||
kitty_placement_remove(struct kitty_context *kc, u_int image_id,
|
||||
u_int placement_id)
|
||||
{
|
||||
struct kitty_source *source;
|
||||
struct kitty_placement **pp, *placement;
|
||||
struct image *im;
|
||||
|
||||
source = kitty_source_find(kc, image_id);
|
||||
if (source == NULL)
|
||||
return (NULL);
|
||||
for (pp = &source->placements; (placement = *pp) != NULL;
|
||||
pp = &placement->next) {
|
||||
if (placement->placement_id != placement_id)
|
||||
continue;
|
||||
im = image_find(placement->server_id);
|
||||
if (im != NULL)
|
||||
image_ref(image_get_id(im));
|
||||
*pp = placement->next;
|
||||
image_free(placement->server_id);
|
||||
free(placement);
|
||||
return (im);
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Replace the virtual image associated with a Kitty source image. */
|
||||
static void
|
||||
kitty_virtual_set(struct kitty_context *kc, u_int id, struct image *im)
|
||||
{
|
||||
struct kitty_source *source;
|
||||
|
||||
source = kitty_source_find(kc, id);
|
||||
if (source == NULL)
|
||||
return;
|
||||
if (source->virtual_id != 0)
|
||||
image_free(source->virtual_id);
|
||||
image_ref(image_get_id(im));
|
||||
source->virtual_id = image_get_id(im);
|
||||
}
|
||||
|
||||
/* Remove and return a Kitty source image. */
|
||||
static struct image *
|
||||
kitty_source_remove(struct kitty_context *kc, u_int id)
|
||||
{
|
||||
struct kitty_source **pp, *source;
|
||||
struct image *im;
|
||||
|
||||
for (pp = &kc->sources; (source = *pp) != NULL; pp = &source->next) {
|
||||
if (source->app_id != id)
|
||||
continue;
|
||||
im = image_find(source->server_id);
|
||||
if (im != NULL)
|
||||
image_ref(image_get_id(im));
|
||||
*pp = source->next;
|
||||
kitty_placements_free(source);
|
||||
if (source->virtual_id != 0)
|
||||
image_free(source->virtual_id);
|
||||
image_free(source->server_id);
|
||||
free(source);
|
||||
return (im);
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Find and reference a Kitty source image. */
|
||||
static struct image *
|
||||
kitty_source_get(struct kitty_context *kc, u_int id)
|
||||
@@ -892,6 +818,104 @@ kitty_source_get(struct kitty_context *kc, u_int id)
|
||||
return (im);
|
||||
}
|
||||
|
||||
/* Remove records for ordinary placements no longer present in the grid. */
|
||||
static u_int
|
||||
kitty_prune_placements(struct kitty_source *source, struct grid *gd)
|
||||
{
|
||||
struct kitty_placement **pp, *placement;
|
||||
u_int removed = 0;
|
||||
|
||||
for (pp = &source->placements; (placement = *pp) != NULL; ) {
|
||||
if (placement->virtual)
|
||||
goto keep;
|
||||
if (image_grid_has_image(gd, placement->server_id))
|
||||
goto keep;
|
||||
*pp = placement->next;
|
||||
image_free(placement->server_id);
|
||||
free(placement);
|
||||
removed++;
|
||||
continue;
|
||||
keep:
|
||||
pp = &placement->next;
|
||||
}
|
||||
return (removed);
|
||||
}
|
||||
|
||||
/* Delete selected placements and release unreferenced image data. */
|
||||
void
|
||||
kitty_delete_images(void *state, struct screen_write_ctx *ctx,
|
||||
const struct kitty_parse_result *result)
|
||||
{
|
||||
struct kitty_context *kc = state;
|
||||
struct grid *gd = ctx->s->grid;
|
||||
struct kitty_source **sp, *source;
|
||||
struct kitty_placement **pp, *placement;
|
||||
u_int removed;
|
||||
u_int placement_id = result->placement_id;
|
||||
char how = result->delete;
|
||||
int selected, release = 0;
|
||||
|
||||
if (kc == NULL)
|
||||
return;
|
||||
if (how >= 'A') {
|
||||
if (how <= 'Z') {
|
||||
how += 'a' - 'A';
|
||||
release = 1;
|
||||
}
|
||||
}
|
||||
if (how == 'r')
|
||||
placement_id = 0;
|
||||
for (source = kc->sources; source != NULL; source = source->next)
|
||||
(void)kitty_prune_placements(source, gd);
|
||||
image_clear_kitty(ctx, result);
|
||||
for (sp = &kc->sources; (source = *sp) != NULL; ) {
|
||||
removed = kitty_prune_placements(source, gd);
|
||||
selected = 0;
|
||||
if (how == 'i') {
|
||||
if (source->app_id == result->image_id)
|
||||
selected = 1;
|
||||
} else if (how == 'r') {
|
||||
if (source->app_id >= result->x) {
|
||||
if (source->app_id <= result->y)
|
||||
selected = 1;
|
||||
}
|
||||
}
|
||||
if (selected) {
|
||||
for (pp = &source->placements;
|
||||
(placement = *pp) != NULL; ) {
|
||||
if (!placement->virtual)
|
||||
goto keep_placement;
|
||||
if (placement_id != 0) {
|
||||
if (placement->placement_id != placement_id)
|
||||
goto keep_placement;
|
||||
}
|
||||
*pp = placement->next;
|
||||
image_free(placement->server_id);
|
||||
free(placement);
|
||||
removed++;
|
||||
continue;
|
||||
keep_placement:
|
||||
pp = &placement->next;
|
||||
}
|
||||
if (placement_id == 0)
|
||||
removed++;
|
||||
}
|
||||
if (!release)
|
||||
goto keep_source;
|
||||
if (removed == 0)
|
||||
goto keep_source;
|
||||
if (source->placements != NULL)
|
||||
goto keep_source;
|
||||
if (image_grid_has_image(gd, source->server_id))
|
||||
goto keep_source;
|
||||
*sp = source->next;
|
||||
kitty_source_free(source);
|
||||
continue;
|
||||
keep_source:
|
||||
sp = &source->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Decode and append one base64-encoded Kitty payload chunk. */
|
||||
static int
|
||||
kitty_append(struct kitty_state *ks, const u_char *buf, size_t len)
|
||||
@@ -949,8 +973,11 @@ kitty_raw(struct kitty_state *ks, u_char *data, size_t size)
|
||||
u_int bytes;
|
||||
|
||||
bytes = (ks->format == 24 ? 3 : 4);
|
||||
if (ks->width == 0 || ks->height == 0 ||
|
||||
(uint64_t)ks->width * ks->height * bytes > IMAGE_SIZE_LIMIT)
|
||||
if (ks->width == 0)
|
||||
return (NULL);
|
||||
if (ks->height == 0)
|
||||
return (NULL);
|
||||
if ((uint64_t)ks->width * ks->height > IMAGE_SIZE_LIMIT / 4)
|
||||
return (NULL);
|
||||
expected = (size_t)ks->width * ks->height * bytes;
|
||||
raw = data;
|
||||
@@ -990,88 +1017,137 @@ static struct image *
|
||||
kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
|
||||
u_int ypixel)
|
||||
{
|
||||
uint64_t numerator, denominator, value;
|
||||
u_int x, y, width, height, sx, sy, canvas_width;
|
||||
u_int canvas_height, cell_width, cell_height, source_width;
|
||||
u_int source_height, display_width, display_height;
|
||||
struct image_view view = { 0 };
|
||||
struct image *im;
|
||||
u_int cell_width, cell_height, source_width, source_height;
|
||||
u_int x_offset, y_offset;
|
||||
uint64_t canvas_width, canvas_height;
|
||||
double width_scale, height_scale, scale = 1, units = 1;
|
||||
double value;
|
||||
int natural_size = 0;
|
||||
|
||||
x = ks->source_x;
|
||||
y = ks->source_y;
|
||||
image_get_size(source, &source_width, &source_height);
|
||||
if (x >= source_width || y >= source_height)
|
||||
view.x = ks->source_x;
|
||||
view.y = ks->source_y;
|
||||
if (view.x >= source_width)
|
||||
return (NULL);
|
||||
width = ks->source_width;
|
||||
if (width == 0 || width > source_width - x)
|
||||
width = source_width - x;
|
||||
height = ks->source_height;
|
||||
if (height == 0 || height > source_height - y)
|
||||
height = source_height - y;
|
||||
if (ks->x_offset > UINT_MAX - width ||
|
||||
ks->y_offset > UINT_MAX - height)
|
||||
if (view.y >= source_height)
|
||||
return (NULL);
|
||||
display_width = width + ks->x_offset;
|
||||
display_height = height + ks->y_offset;
|
||||
|
||||
view.width = source_width - view.x;
|
||||
if (ks->source_width != 0) {
|
||||
if (view.width > ks->source_width)
|
||||
view.width = ks->source_width;
|
||||
}
|
||||
view.height = source_height - view.y;
|
||||
if (ks->source_height != 0) {
|
||||
if (view.height > ks->source_height)
|
||||
view.height = ks->source_height;
|
||||
}
|
||||
cell_width = (xpixel == 0 ? 8 : xpixel);
|
||||
cell_height = (ypixel == 0 ? 16 : ypixel);
|
||||
if (ks->columns == 0 && ks->rows == 0) {
|
||||
image_size_in_cells(display_width, display_height, cell_width,
|
||||
cell_height,
|
||||
&sx, &sy);
|
||||
value = (uint64_t)sx * cell_width;
|
||||
if (value > UINT_MAX)
|
||||
return (NULL);
|
||||
canvas_width = value;
|
||||
value = (uint64_t)sy * cell_height;
|
||||
if (value > UINT_MAX)
|
||||
return (NULL);
|
||||
canvas_height = value;
|
||||
} else if (ks->columns != 0 && ks->rows != 0) {
|
||||
sx = ks->columns;
|
||||
sy = ks->rows;
|
||||
canvas_width = display_width;
|
||||
canvas_height = display_height;
|
||||
} else if (ks->columns != 0) {
|
||||
sx = ks->columns;
|
||||
numerator = (uint64_t)display_height * sx * cell_width;
|
||||
denominator = (uint64_t)display_width * cell_height;
|
||||
value = (numerator + denominator - 1) / denominator;
|
||||
if (value == 0 || value > UINT_MAX)
|
||||
return (NULL);
|
||||
sy = value;
|
||||
canvas_width = display_width;
|
||||
numerator = (uint64_t)sy * cell_height * display_width;
|
||||
denominator = (uint64_t)sx * cell_width;
|
||||
value = (numerator + denominator - 1) / denominator;
|
||||
if (value < display_height)
|
||||
value = display_height;
|
||||
if (value > UINT_MAX)
|
||||
return (NULL);
|
||||
canvas_height = value;
|
||||
} else {
|
||||
sy = ks->rows;
|
||||
numerator = (uint64_t)display_width * sy * cell_height;
|
||||
denominator = (uint64_t)display_height * cell_width;
|
||||
value = (numerator + denominator - 1) / denominator;
|
||||
if (value == 0 || value > UINT_MAX)
|
||||
return (NULL);
|
||||
sx = value;
|
||||
canvas_height = display_height;
|
||||
numerator = (uint64_t)sx * cell_width * display_height;
|
||||
denominator = (uint64_t)sy * cell_height;
|
||||
value = (numerator + denominator - 1) / denominator;
|
||||
if (value < display_width)
|
||||
value = display_width;
|
||||
if (value > UINT_MAX)
|
||||
return (NULL);
|
||||
canvas_width = value;
|
||||
x_offset = ks->x_offset;
|
||||
y_offset = ks->y_offset;
|
||||
/* Match Kitty by clamping offsets to the starting cell. */
|
||||
if (x_offset >= cell_width)
|
||||
x_offset = cell_width - 1;
|
||||
if (y_offset >= cell_height)
|
||||
y_offset = cell_height - 1;
|
||||
view.sx = ks->columns;
|
||||
view.sy = ks->rows;
|
||||
if (view.sx > USHRT_MAX)
|
||||
return (NULL);
|
||||
if (view.sy > USHRT_MAX)
|
||||
return (NULL);
|
||||
if (view.sx == 0) {
|
||||
if (view.sy == 0)
|
||||
natural_size = 1;
|
||||
}
|
||||
if (natural_size) {
|
||||
if (x_offset > UINT_MAX - view.width)
|
||||
return (NULL);
|
||||
if (y_offset > UINT_MAX - view.height)
|
||||
return (NULL);
|
||||
image_size_in_cells(view.width + x_offset,
|
||||
view.height + y_offset, cell_width, cell_height,
|
||||
&view.sx, &view.sy);
|
||||
} else if (view.sy == 0) {
|
||||
scale = ((double)view.sx * cell_width - x_offset) / view.width;
|
||||
value = ceil((view.height * scale + y_offset) / cell_height);
|
||||
if (value > USHRT_MAX)
|
||||
return (NULL);
|
||||
view.sy = value;
|
||||
} else if (view.sx == 0) {
|
||||
scale = ((double)view.sy * cell_height - y_offset) /
|
||||
view.height;
|
||||
value = ceil((view.width * scale + x_offset) / cell_width);
|
||||
if (value > USHRT_MAX)
|
||||
return (NULL);
|
||||
view.sx = value;
|
||||
} else {
|
||||
width_scale = ((double)view.sx * cell_width - x_offset) /
|
||||
view.width;
|
||||
height_scale = ((double)view.sy * cell_height - y_offset) /
|
||||
view.height;
|
||||
scale = width_scale;
|
||||
if (height_scale < scale)
|
||||
scale = height_scale;
|
||||
}
|
||||
canvas_width = (uint64_t)view.sx * cell_width;
|
||||
canvas_height = (uint64_t)view.sy * cell_height;
|
||||
/* Preserve source resolution when shrinking the image. */
|
||||
if (scale < 1)
|
||||
units = 1 / scale;
|
||||
value = ceil(canvas_width * units);
|
||||
if (value > UINT_MAX)
|
||||
return (NULL);
|
||||
view.canvas_width = value;
|
||||
value = ceil(canvas_height * units);
|
||||
if (value > UINT_MAX)
|
||||
return (NULL);
|
||||
view.canvas_height = value;
|
||||
view.scaled_width = floor(view.width * scale * units);
|
||||
if (view.scaled_width < view.width)
|
||||
view.scaled_width = view.width;
|
||||
view.scaled_height = floor(view.height * scale * units);
|
||||
if (view.scaled_height < view.height)
|
||||
view.scaled_height = view.height;
|
||||
view.x_offset = floor(x_offset * units);
|
||||
view.y_offset = floor(y_offset * units);
|
||||
if (view.x_offset > view.canvas_width)
|
||||
return (NULL);
|
||||
if (view.scaled_width > view.canvas_width - view.x_offset)
|
||||
return (NULL);
|
||||
if (view.y_offset > view.canvas_height)
|
||||
return (NULL);
|
||||
if (view.scaled_height > view.canvas_height - view.y_offset)
|
||||
return (NULL);
|
||||
if (ks->columns != 0) {
|
||||
if (ks->rows != 0) {
|
||||
view.x_offset += (view.canvas_width - view.x_offset -
|
||||
view.scaled_width) / 2;
|
||||
view.y_offset += (view.canvas_height - view.y_offset -
|
||||
view.scaled_height) / 2;
|
||||
}
|
||||
}
|
||||
/* A matching rectangle needs neither resampling nor padding. */
|
||||
if (x_offset == 0) {
|
||||
if (y_offset == 0) {
|
||||
if ((uint64_t)view.width * canvas_height ==
|
||||
(uint64_t)view.height * canvas_width) {
|
||||
view.canvas_width = view.width;
|
||||
view.scaled_width = view.width;
|
||||
view.canvas_height = view.height;
|
||||
view.scaled_height = view.height;
|
||||
view.x_offset = 0;
|
||||
view.y_offset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
im = image_create_view(source, &view);
|
||||
if (im != NULL) {
|
||||
if (ks->no_cursor)
|
||||
image_set_no_cursor(im);
|
||||
}
|
||||
|
||||
im = image_create_view(source, x, y, width, height, canvas_width,
|
||||
canvas_height, sx, sy, ks->x_offset, ks->y_offset);
|
||||
if (im != NULL && ks->no_cursor)
|
||||
image_set_no_cursor(im);
|
||||
return (im);
|
||||
}
|
||||
|
||||
@@ -1086,6 +1162,7 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
|
||||
{
|
||||
struct kitty_context *kc = *state;
|
||||
struct kitty_state *ks;
|
||||
struct kitty_state command = { .action = 't' };
|
||||
const u_char *semi;
|
||||
u_char *decoded, *pixels;
|
||||
u_char *uncompressed;
|
||||
@@ -1094,7 +1171,6 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
|
||||
u_int sx, sy, cell_width, cell_height;
|
||||
uint64_t canvas_width, canvas_height;
|
||||
struct image *im = NULL, *source;
|
||||
struct kitty_source *stored;
|
||||
int error;
|
||||
|
||||
if (kc == NULL) {
|
||||
@@ -1107,6 +1183,14 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
|
||||
semi = memchr(buf, ';', len);
|
||||
controllen = (semi == NULL ? len : (size_t)(semi - buf));
|
||||
payloadlen = (semi == NULL ? 0 : len - controllen - 1);
|
||||
if (ks != NULL) {
|
||||
/* A delete command aborts an incomplete upload. */
|
||||
(void)kitty_control(&command, buf, controllen);
|
||||
if (command.action == 'd') {
|
||||
kitty_state_free(ks);
|
||||
kc->transfer = ks = NULL;
|
||||
}
|
||||
}
|
||||
if (ks == NULL) {
|
||||
ks = xcalloc(1, sizeof *ks);
|
||||
ks->action = 't';
|
||||
@@ -1116,18 +1200,35 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
|
||||
}
|
||||
ks->more = 0;
|
||||
error = kitty_control(ks, buf, controllen);
|
||||
if (ks->image_id == 0)
|
||||
ks->placement_id = 0;
|
||||
if (ks->image_id == 0) {
|
||||
if (ks->image_number == 0)
|
||||
ks->placement_id = 0;
|
||||
}
|
||||
result->image_id = ks->image_id;
|
||||
result->image_number = ks->image_number;
|
||||
result->quiet = ks->quiet;
|
||||
result->action = ks->action;
|
||||
result->delete = ks->delete;
|
||||
result->placement_id = ks->placement_id;
|
||||
result->z = ks->z;
|
||||
result->x = ks->source_x;
|
||||
result->y = ks->source_y;
|
||||
if (error != 0)
|
||||
goto fail;
|
||||
if (ks->medium != 'd')
|
||||
if (ks->image_number != 0) {
|
||||
if (ks->image_id != 0)
|
||||
goto fail;
|
||||
result->status = KITTY_PARSE_UNSUPPORTED;
|
||||
goto fail;
|
||||
}
|
||||
if (ks->unsupported) {
|
||||
result->status = KITTY_PARSE_UNSUPPORTED;
|
||||
goto fail;
|
||||
}
|
||||
if (ks->medium != 'd') {
|
||||
result->status = KITTY_PARSE_UNSUPPORTED;
|
||||
goto fail;
|
||||
}
|
||||
if (payloadlen != 0) {
|
||||
if (kitty_append(ks, semi + 1, payloadlen) != 0)
|
||||
goto fail;
|
||||
@@ -1147,7 +1248,7 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
|
||||
im = kitty_place_image(source, ks, xpixel, ypixel);
|
||||
image_free(image_get_id(source));
|
||||
if (im != NULL) {
|
||||
kitty_virtual_set(kc, ks->image_id, im);
|
||||
(void)kitty_placement_set(kc, ks, im);
|
||||
image_free(image_get_id(im));
|
||||
im = NULL;
|
||||
result->action = 'u';
|
||||
@@ -1157,8 +1258,8 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
|
||||
im = kitty_place_image(source, ks, xpixel, ypixel);
|
||||
image_free(image_get_id(source));
|
||||
if (im != NULL) {
|
||||
result->replace_id = kitty_placement_set(kc, ks->image_id,
|
||||
ks->placement_id, ks->z, im);
|
||||
result->replace_id =
|
||||
kitty_placement_set(kc, ks, im);
|
||||
result->status = KITTY_PARSE_OK;
|
||||
}
|
||||
}
|
||||
@@ -1166,39 +1267,30 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
|
||||
return (im);
|
||||
}
|
||||
if (ks->action == 'd') {
|
||||
if (ks->delete == 'a' || ks->delete == 'A') {
|
||||
kitty_placements_free_all(kc);
|
||||
im = NULL;
|
||||
} else if (ks->delete == 'i') {
|
||||
if (ks->placement_id != 0)
|
||||
im = kitty_placement_remove(kc, ks->image_id,
|
||||
ks->placement_id);
|
||||
else {
|
||||
source = kitty_source_get(kc, ks->image_id);
|
||||
if (source != NULL) {
|
||||
stored = kitty_source_find(kc,
|
||||
ks->image_id);
|
||||
kitty_placements_free(stored);
|
||||
}
|
||||
im = source;
|
||||
}
|
||||
} else if (ks->delete == 'I')
|
||||
im = kitty_source_remove(kc, ks->image_id);
|
||||
else if (ks->delete == 'z' || ks->delete == 'Z') {
|
||||
kitty_placements_remove_z(kc, ks->z);
|
||||
im = NULL;
|
||||
} else
|
||||
goto fail;
|
||||
if ((ks->delete == 'i' || ks->delete == 'I') &&
|
||||
im == NULL)
|
||||
result->status = KITTY_PARSE_MISSING;
|
||||
else
|
||||
switch (ks->delete) {
|
||||
case 'a': case 'A': case 'i': case 'I':
|
||||
case 'c': case 'C': case 'p': case 'P':
|
||||
case 'q': case 'Q': case 'r': case 'R':
|
||||
case 'x': case 'X': case 'y': case 'Y':
|
||||
case 'z': case 'Z':
|
||||
result->status = KITTY_PARSE_OK;
|
||||
break;
|
||||
default:
|
||||
result->status = KITTY_PARSE_UNSUPPORTED;
|
||||
break;
|
||||
}
|
||||
kitty_state_free(ks);
|
||||
return (im);
|
||||
return (NULL);
|
||||
}
|
||||
if (ks->action != 'T' && ks->action != 't' && ks->action != 'q')
|
||||
switch (ks->action) {
|
||||
case 'T': case 't': case 'q':
|
||||
break;
|
||||
case 'f': case 'a': case 'c':
|
||||
result->status = KITTY_PARSE_UNSUPPORTED;
|
||||
/* FALLTHROUGH */
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
decoded = ks->raw;
|
||||
decodedlen = ks->rawlen;
|
||||
@@ -1263,15 +1355,14 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
|
||||
im = kitty_place_image(source, ks, xpixel, ypixel);
|
||||
if (im == NULL)
|
||||
result->status = KITTY_PARSE_ERROR;
|
||||
else if (ks->placement_id != 0)
|
||||
(void)kitty_placement_set(kc, ks->image_id,
|
||||
ks->placement_id, ks->z, im);
|
||||
else
|
||||
(void)kitty_placement_set(kc, ks, im);
|
||||
} else if (ks->virtual) {
|
||||
im = kitty_place_image(source, ks, xpixel, ypixel);
|
||||
if (im == NULL)
|
||||
result->status = KITTY_PARSE_ERROR;
|
||||
else {
|
||||
kitty_virtual_set(kc, ks->image_id, im);
|
||||
(void)kitty_placement_set(kc, ks, im);
|
||||
image_free(image_get_id(im));
|
||||
im = NULL;
|
||||
result->action = 'u';
|
||||
@@ -1344,6 +1435,44 @@ kitty_placeholder_index(uint32_t value, u_int *index)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Return whether a cell starts with a Kitty Unicode placeholder. */
|
||||
int
|
||||
kitty_cell_is_placeholder(const struct grid_cell *gc)
|
||||
{
|
||||
if (gc->data.size < 4)
|
||||
return (0);
|
||||
if (gc->data.data[0] != 0xf4)
|
||||
return (0);
|
||||
if (gc->data.data[1] != 0x8e)
|
||||
return (0);
|
||||
if (gc->data.data[2] != 0xbb)
|
||||
return (0);
|
||||
if (gc->data.data[3] != 0xae)
|
||||
return (0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Decode an image or placement ID from a placeholder colour. */
|
||||
static int
|
||||
kitty_colour_id(int colour, u_int *id)
|
||||
{
|
||||
if (colour < 0)
|
||||
return (0);
|
||||
if (colour & COLOUR_FLAG_RGB)
|
||||
*id = colour & 0xffffff;
|
||||
else if (colour & COLOUR_FLAG_256)
|
||||
*id = colour & 0xff;
|
||||
else if (colour < 8)
|
||||
*id = colour;
|
||||
else if (colour >= 90) {
|
||||
if (colour >= 98)
|
||||
return (0);
|
||||
*id = colour - 90 + 8;
|
||||
} else
|
||||
return (0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Resolve a Kitty Unicode placeholder to an image and source cell. */
|
||||
int
|
||||
kitty_placeholder_to_image(void *state, struct grid *gd, struct grid_cell *gc,
|
||||
@@ -1351,60 +1480,95 @@ kitty_placeholder_to_image(void *state, struct grid *gd, struct grid_cell *gc,
|
||||
{
|
||||
struct kitty_context *kc = state;
|
||||
struct kitty_source *source;
|
||||
struct kitty_placement *placement;
|
||||
struct kitty_placeholder left;
|
||||
struct grid_cell left_cell;
|
||||
struct image *im;
|
||||
uint32_t value;
|
||||
size_t offset = 0;
|
||||
u_int values[3], nvalues = 0, id, x, y, sx, sy;
|
||||
u_int left_x, left_y;
|
||||
u_int values[3], nvalues = 0, id, x = 0, y = 0;
|
||||
u_int sx, sy, placement_id = 0;
|
||||
int inherit = 0;
|
||||
|
||||
if (kc == NULL ||
|
||||
!kitty_placeholder_character(gc->data.data, gc->data.size, &offset,
|
||||
&value) || value != 0x10eeee)
|
||||
if (kc == NULL)
|
||||
return (0);
|
||||
if (!kitty_placeholder_character(gc->data.data, gc->data.size, &offset,
|
||||
&value))
|
||||
return (0);
|
||||
if (value != 0x10eeee)
|
||||
return (0);
|
||||
while (offset < gc->data.size && nvalues < nitems(values)) {
|
||||
if (!kitty_placeholder_character(gc->data.data, gc->data.size,
|
||||
&offset, &value) ||
|
||||
!kitty_placeholder_index(value, &values[nvalues]))
|
||||
&offset, &value))
|
||||
return (0);
|
||||
if (!kitty_placeholder_index(value, &values[nvalues]))
|
||||
return (0);
|
||||
nvalues++;
|
||||
}
|
||||
if (offset != gc->data.size)
|
||||
return (0);
|
||||
|
||||
if (gc->fg & COLOUR_FLAG_RGB)
|
||||
id = gc->fg & 0xffffff;
|
||||
else if (gc->fg >= 0 && gc->fg <= 255)
|
||||
id = gc->fg;
|
||||
else
|
||||
if (!kitty_colour_id(gc->fg, &id))
|
||||
return (0);
|
||||
if (nvalues == 3)
|
||||
(void)kitty_colour_id(gc->us, &placement_id);
|
||||
if (nvalues >= 1)
|
||||
y = values[0];
|
||||
if (nvalues >= 2)
|
||||
x = values[1];
|
||||
if (grid_x != 0) {
|
||||
grid_view_get_cell(gd, grid_x - 1, grid_y, &left_cell);
|
||||
if (left_cell.fg == gc->fg) {
|
||||
if (left_cell.us == gc->us) {
|
||||
inherit = image_grid_get_placeholder(gd,
|
||||
grid_x - 1, gd->hsize + grid_y, &left);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inherit) {
|
||||
if (nvalues >= 1) {
|
||||
if (left.source_y != y)
|
||||
inherit = 0;
|
||||
}
|
||||
if (nvalues >= 2) {
|
||||
if (left.source_x + 1 != x)
|
||||
inherit = 0;
|
||||
}
|
||||
}
|
||||
if (inherit) {
|
||||
if (nvalues == 0)
|
||||
y = left.source_y;
|
||||
if (nvalues < 2)
|
||||
x = left.source_x + 1;
|
||||
if (nvalues < 3)
|
||||
id |= left.image_id & 0xff000000;
|
||||
}
|
||||
if (nvalues == 3) {
|
||||
if (values[2] > 255)
|
||||
return (0);
|
||||
id |= values[2] << 24;
|
||||
}
|
||||
source = kitty_source_find(kc, id);
|
||||
if (source == NULL)
|
||||
return (0);
|
||||
im = image_find(source->virtual_id != 0 ? source->virtual_id :
|
||||
source->server_id);
|
||||
for (placement = source->placements; placement != NULL;
|
||||
placement = placement->next) {
|
||||
if (!placement->virtual)
|
||||
continue;
|
||||
if (placement_id != 0) {
|
||||
if (placement->placement_id != placement_id)
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (placement != NULL) {
|
||||
im = image_find(placement->server_id);
|
||||
placeholder->z = placement->z;
|
||||
placement_id = placement->placement_id;
|
||||
} else
|
||||
return (0);
|
||||
if (im == NULL)
|
||||
return (0);
|
||||
image_get_size_in_cells(im, &sx, &sy);
|
||||
|
||||
if (nvalues >= 1)
|
||||
y = values[0];
|
||||
else {
|
||||
if (grid_x == 0 || !image_grid_get_source(gd, grid_x - 1,
|
||||
gd->hsize + grid_y, im, &left_x, &left_y))
|
||||
return (0);
|
||||
y = left_y;
|
||||
}
|
||||
if (nvalues >= 2)
|
||||
x = values[1];
|
||||
else {
|
||||
if (grid_x == 0 || !image_grid_get_source(gd, grid_x - 1,
|
||||
gd->hsize + grid_y, im, &left_x, &left_y) ||
|
||||
left_x == UINT_MAX)
|
||||
return (0);
|
||||
x = left_x + 1;
|
||||
}
|
||||
if (x >= sx || y >= sy)
|
||||
return (0);
|
||||
|
||||
@@ -1412,11 +1576,6 @@ kitty_placeholder_to_image(void *state, struct grid *gd, struct grid_cell *gc,
|
||||
placeholder->source_x = x;
|
||||
placeholder->source_y = y;
|
||||
placeholder->image_id = id;
|
||||
if (gc->us & COLOUR_FLAG_RGB)
|
||||
placeholder->placement_id = gc->us & 0xffffff;
|
||||
else
|
||||
placeholder->placement_id = 0;
|
||||
placeholder->z = 0;
|
||||
utf8_set(&gc->data, ' ');
|
||||
placeholder->placement_id = placement_id;
|
||||
return (1);
|
||||
}
|
||||
|
||||
317
image.c
317
image.c
@@ -63,6 +63,7 @@ struct image_line {
|
||||
|
||||
enum image_input {
|
||||
IMAGE_INPUT_ALL = -1,
|
||||
IMAGE_INPUT_OVERWRITTEN = -2,
|
||||
IMAGE_INPUT_SIXEL,
|
||||
IMAGE_INPUT_KITTY
|
||||
};
|
||||
@@ -72,6 +73,7 @@ struct image_placement {
|
||||
struct image_store *store;
|
||||
struct image *image;
|
||||
enum image_input input;
|
||||
int placeholder;
|
||||
u_int app_image_id;
|
||||
u_int app_placement_id;
|
||||
int32_t z;
|
||||
@@ -391,8 +393,15 @@ image_line_remove(struct image_line *line, u_int x, u_int width,
|
||||
TAILQ_FOREACH_SAFE(span, &line->spans, line_entry, next) {
|
||||
if (only != NULL && span->placement != only)
|
||||
continue;
|
||||
if (input != IMAGE_INPUT_ALL && span->placement->input != input)
|
||||
continue;
|
||||
if (input == IMAGE_INPUT_OVERWRITTEN) {
|
||||
if (span->placement->input != IMAGE_INPUT_SIXEL) {
|
||||
if (!span->placement->placeholder)
|
||||
continue;
|
||||
}
|
||||
} else if (input != IMAGE_INPUT_ALL) {
|
||||
if (span->placement->input != input)
|
||||
continue;
|
||||
}
|
||||
span_end = span->x + span->sx;
|
||||
if (span_end <= x || span->x >= end)
|
||||
continue;
|
||||
@@ -431,10 +440,10 @@ image_store_prune(struct image_store *store)
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove SIXEL spans overwritten by text. */
|
||||
/* Remove SIXEL and placeholder spans overwritten by text. */
|
||||
void
|
||||
image_grid_remove_sixel_spans(struct grid *gd, u_int x, u_int y, u_int width,
|
||||
u_int height)
|
||||
image_grid_remove_overwritten_spans(struct grid *gd, u_int x, u_int y,
|
||||
u_int width, u_int height)
|
||||
{
|
||||
u_int row;
|
||||
|
||||
@@ -446,7 +455,7 @@ image_grid_remove_sixel_spans(struct grid *gd, u_int x, u_int y, u_int width,
|
||||
height = gd->hsize + gd->sy - y;
|
||||
for (row = y; row < y + height; row++)
|
||||
image_line_remove(gd->linedata[row].images, x, width,
|
||||
IMAGE_INPUT_SIXEL, NULL);
|
||||
IMAGE_INPUT_OVERWRITTEN, NULL);
|
||||
image_store_prune(gd->images);
|
||||
}
|
||||
|
||||
@@ -536,6 +545,7 @@ image_copy_placement(struct image_copy_ctx *ctx,
|
||||
placement = image_placement_create(ctx->destination, source->image,
|
||||
source->input, source->app_image_id, source->app_placement_id,
|
||||
source->z);
|
||||
placement->placeholder = source->placeholder;
|
||||
ctx->maps = xreallocarray(ctx->maps, ctx->count + 1,
|
||||
sizeof *ctx->maps);
|
||||
ctx->maps[ctx->count].source = source;
|
||||
@@ -651,10 +661,10 @@ image_grid_area_has_images(struct grid *gd, u_int x, u_int y, u_int width,
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Find source coordinates for an image span at one grid cell. */
|
||||
/* Find the resolved placeholder at one grid cell. */
|
||||
int
|
||||
image_grid_get_source(struct grid *gd, u_int x, u_int y, struct image *im,
|
||||
u_int *source_x, u_int *source_y)
|
||||
image_grid_get_placeholder(struct grid *gd, u_int x, u_int y,
|
||||
struct kitty_placeholder *placeholder)
|
||||
{
|
||||
struct image_line *line;
|
||||
struct image_span *span, *found = NULL;
|
||||
@@ -663,14 +673,21 @@ image_grid_get_source(struct grid *gd, u_int x, u_int y, struct image *im,
|
||||
(line = gd->linedata[y].images) == NULL)
|
||||
return (0);
|
||||
TAILQ_FOREACH(span, &line->spans, line_entry) {
|
||||
if (span->placement->image == im && x >= span->x &&
|
||||
x < span->x + span->sx)
|
||||
if (!span->placement->placeholder)
|
||||
continue;
|
||||
if (x < span->x)
|
||||
continue;
|
||||
if (x < span->x + span->sx)
|
||||
found = span;
|
||||
}
|
||||
if (found == NULL)
|
||||
return (0);
|
||||
*source_x = found->source_x + x - found->x;
|
||||
*source_y = found->source_y;
|
||||
placeholder->image = found->placement->image;
|
||||
placeholder->source_x = found->source_x + x - found->x;
|
||||
placeholder->source_y = found->source_y;
|
||||
placeholder->image_id = found->placement->app_image_id;
|
||||
placeholder->placement_id = found->placement->app_placement_id;
|
||||
placeholder->z = found->placement->z;
|
||||
return (1);
|
||||
}
|
||||
|
||||
@@ -688,6 +705,8 @@ image_place_cell_kitty(struct screen_write_ctx *ctx, struct image *im,
|
||||
|
||||
TAILQ_FOREACH_REVERSE(candidate, &store->placements,
|
||||
image_placements, entry) {
|
||||
if (!candidate->placeholder)
|
||||
continue;
|
||||
if (candidate->input == IMAGE_INPUT_KITTY &&
|
||||
candidate->image == im &&
|
||||
candidate->app_image_id == image_id &&
|
||||
@@ -697,9 +716,11 @@ image_place_cell_kitty(struct screen_write_ctx *ctx, struct image *im,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (placement == NULL)
|
||||
if (placement == NULL) {
|
||||
placement = image_placement_create(gd, im, IMAGE_INPUT_KITTY,
|
||||
image_id, placement_id, z);
|
||||
placement->placeholder = 1;
|
||||
}
|
||||
line = image_line_get(&gd->linedata[gd->hsize + y]);
|
||||
TAILQ_FOREACH(span, &line->spans, line_entry) {
|
||||
if (span->placement == placement && span->x + span->sx == x &&
|
||||
@@ -950,52 +971,109 @@ image_create(u_int width, u_int height, u_int canvas_width,
|
||||
if ((uint64_t)sx * sy > SIZE_MAX / sizeof *im->cells ||
|
||||
sx > USHRT_MAX || sy > USHRT_MAX)
|
||||
return (NULL);
|
||||
if ((uint64_t)sx * sy > IMAGE_SIZE_LIMIT / sizeof *im->cells)
|
||||
return (NULL);
|
||||
im = image_alloc(width, height, canvas_width, canvas_height, sx, sy,
|
||||
(size_t)width * 4, pixels);
|
||||
im->flags |= IMAGE_FLAG_OWN_PIXELS;
|
||||
return (im);
|
||||
}
|
||||
|
||||
/* Create a cell-aligned view of an existing image with an optional offset. */
|
||||
/* Create a cropped and scaled image view with transparent padding. */
|
||||
struct image *
|
||||
image_create_view(struct image *source, u_int x, u_int y, u_int width,
|
||||
u_int height, u_int canvas_width, u_int canvas_height, u_int sx, u_int sy,
|
||||
u_int x_offset, u_int y_offset)
|
||||
image_create_view(struct image *source, const struct image_view *view)
|
||||
{
|
||||
struct image *im;
|
||||
u_char *pixels;
|
||||
u_int padded_width, padded_height, yy;
|
||||
u_char *pixels, *row;
|
||||
const u_char *source_row;
|
||||
u_int padded_width, padded_height, x, y, source_x, source_y;
|
||||
int share_pixels = 1;
|
||||
|
||||
if (source == NULL || x >= source->width || y >= source->height ||
|
||||
width == 0 || width > source->width - x || height == 0 ||
|
||||
height > source->height - y || canvas_width < width ||
|
||||
canvas_height < height || sx == 0 || sy == 0)
|
||||
if (source == NULL)
|
||||
return (NULL);
|
||||
if ((uint64_t)sx * sy > SIZE_MAX / sizeof *im->cells ||
|
||||
sx > USHRT_MAX || sy > USHRT_MAX)
|
||||
if (view->x >= source->width)
|
||||
return (NULL);
|
||||
if (view->y >= source->height)
|
||||
return (NULL);
|
||||
if (view->width == 0)
|
||||
return (NULL);
|
||||
if (view->width > source->width - view->x)
|
||||
return (NULL);
|
||||
if (view->height == 0)
|
||||
return (NULL);
|
||||
if (view->height > source->height - view->y)
|
||||
return (NULL);
|
||||
if (view->scaled_width == 0)
|
||||
return (NULL);
|
||||
if (view->scaled_height == 0)
|
||||
return (NULL);
|
||||
if (view->sx == 0)
|
||||
return (NULL);
|
||||
if (view->sy == 0)
|
||||
return (NULL);
|
||||
if (view->sx > USHRT_MAX)
|
||||
return (NULL);
|
||||
if (view->sy > USHRT_MAX)
|
||||
return (NULL);
|
||||
if ((uint64_t)view->sx * view->sy > SIZE_MAX / sizeof *im->cells)
|
||||
return (NULL);
|
||||
if ((uint64_t)view->sx * view->sy >
|
||||
IMAGE_SIZE_LIMIT / sizeof *im->cells)
|
||||
return (NULL);
|
||||
if (view->x_offset > UINT_MAX - view->scaled_width)
|
||||
return (NULL);
|
||||
if (view->y_offset > UINT_MAX - view->scaled_height)
|
||||
return (NULL);
|
||||
padded_width = view->scaled_width + view->x_offset;
|
||||
padded_height = view->scaled_height + view->y_offset;
|
||||
if (padded_width > view->canvas_width)
|
||||
return (NULL);
|
||||
if (padded_height > view->canvas_height)
|
||||
return (NULL);
|
||||
|
||||
if (x_offset == 0 && y_offset == 0) {
|
||||
im = image_alloc(width, height, canvas_width, canvas_height,
|
||||
sx, sy, source->stride, source->pixels +
|
||||
(size_t)y * source->stride + (size_t)x * 4);
|
||||
/* Share pixels when no scaling or padding is needed. */
|
||||
if (view->scaled_width != view->width)
|
||||
share_pixels = 0;
|
||||
if (view->scaled_height != view->height)
|
||||
share_pixels = 0;
|
||||
if (view->x_offset != 0)
|
||||
share_pixels = 0;
|
||||
if (view->y_offset != 0)
|
||||
share_pixels = 0;
|
||||
if (share_pixels) {
|
||||
im = image_alloc(view->width, view->height, view->canvas_width,
|
||||
view->canvas_height, view->sx, view->sy, source->stride,
|
||||
source->pixels + (size_t)view->y * source->stride +
|
||||
(size_t)view->x * 4);
|
||||
} else {
|
||||
if (x_offset > UINT_MAX - width ||
|
||||
y_offset > UINT_MAX - height)
|
||||
return (NULL);
|
||||
padded_width = width + x_offset;
|
||||
padded_height = height + y_offset;
|
||||
if ((uint64_t)padded_width * padded_height * 4 > SIZE_MAX)
|
||||
if ((uint64_t)padded_width * padded_height >
|
||||
IMAGE_SIZE_LIMIT / 4)
|
||||
return (NULL);
|
||||
pixels = xcalloc((size_t)padded_width * padded_height, 4);
|
||||
for (yy = 0; yy < height; yy++) {
|
||||
memcpy(pixels + (size_t)(yy + y_offset) * padded_width * 4 +
|
||||
(size_t)x_offset * 4,
|
||||
source->pixels + (size_t)(y + yy) * source->stride +
|
||||
(size_t)x * 4, (size_t)width * 4);
|
||||
for (y = 0; y < view->scaled_height; y++) {
|
||||
source_y = view->y + (uint64_t)y * view->height /
|
||||
view->scaled_height;
|
||||
source_row = source->pixels +
|
||||
(size_t)source_y * source->stride;
|
||||
source_row += (size_t)view->x * 4;
|
||||
row = pixels +
|
||||
((size_t)(y + view->y_offset) * padded_width +
|
||||
view->x_offset) * 4;
|
||||
if (view->scaled_width == view->width) {
|
||||
memcpy(row, source_row,
|
||||
(size_t)view->width * 4);
|
||||
continue;
|
||||
}
|
||||
for (x = 0; x < view->scaled_width; x++) {
|
||||
source_x = (uint64_t)x * view->width /
|
||||
view->scaled_width;
|
||||
memcpy(row + (size_t)x * 4,
|
||||
source_row + (size_t)source_x * 4, 4);
|
||||
}
|
||||
}
|
||||
im = image_alloc(padded_width, padded_height, canvas_width,
|
||||
canvas_height, sx, sy, (size_t)padded_width * 4, pixels);
|
||||
im = image_alloc(padded_width, padded_height,
|
||||
view->canvas_width, view->canvas_height, view->sx, view->sy,
|
||||
(size_t)padded_width * 4, pixels);
|
||||
im->flags |= IMAGE_FLAG_OWN_PIXELS;
|
||||
}
|
||||
im->parent_id = source->id;
|
||||
@@ -1075,8 +1153,12 @@ image_get_fallback_at(struct tty *tty, struct screen *s, u_int x, u_int y,
|
||||
return (-1);
|
||||
placement = found->placement;
|
||||
if (placement->input == IMAGE_INPUT_KITTY && placement->z < 0) {
|
||||
if (gc->data.size != 1 || gc->data.data[0] != ' ')
|
||||
return (-1);
|
||||
if (!kitty_cell_is_placeholder(gc)) {
|
||||
if (gc->data.size != 1)
|
||||
return (-1);
|
||||
if (gc->data.data[0] != ' ')
|
||||
return (-1);
|
||||
}
|
||||
if (placement->z < IMAGE_Z_BELOW_BACKGROUND &&
|
||||
!COLOUR_DEFAULT(gc->bg))
|
||||
return (-1);
|
||||
@@ -1227,44 +1309,151 @@ image_clear(struct screen_write_ctx *ctx, u_int id)
|
||||
if (store == NULL)
|
||||
return;
|
||||
TAILQ_FOREACH_SAFE(placement, &store->placements, entry, next) {
|
||||
if (id != 0 && placement->image->id != id &&
|
||||
placement->image->source_id != id)
|
||||
continue;
|
||||
if (id != 0) {
|
||||
if (placement->image->source_id != id) {
|
||||
if (placement->placeholder)
|
||||
continue;
|
||||
if (placement->image->id != id)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
image_remove_placement(placement);
|
||||
}
|
||||
if (ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
}
|
||||
|
||||
/* Clear Kitty placements selected by application identity or z-index. */
|
||||
void
|
||||
image_clear_kitty(struct screen_write_ctx *ctx, char how, u_int image_id,
|
||||
u_int placement_id, int32_t z)
|
||||
/* Return whether a grid still references an image or its source. */
|
||||
int
|
||||
image_grid_has_image(struct grid *gd, u_int id)
|
||||
{
|
||||
struct image_store *store = ctx->s->grid->images;
|
||||
struct image_placement *placement;
|
||||
|
||||
if (gd->images == NULL)
|
||||
return (0);
|
||||
TAILQ_FOREACH(placement, &gd->images->placements, entry) {
|
||||
if (placement->image->id == id)
|
||||
return (1);
|
||||
if (placement->image->source_id == id)
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Return whether a placement intersects the selected screen cells. */
|
||||
static int
|
||||
image_placement_intersects(struct grid *gd, struct image_placement *placement,
|
||||
const struct kitty_parse_result *result)
|
||||
{
|
||||
struct image_line *line;
|
||||
struct image_span *span;
|
||||
u_int x = result->x, y = result->y, row;
|
||||
char how = result->delete;
|
||||
int match_x = 0, match_y = 0;
|
||||
|
||||
if (how >= 'A') {
|
||||
if (how <= 'Z')
|
||||
how += 'a' - 'A';
|
||||
}
|
||||
switch (how) {
|
||||
case 'p': case 'q': case 'c':
|
||||
match_y = 1;
|
||||
/* FALLTHROUGH */
|
||||
case 'x':
|
||||
match_x = 1;
|
||||
break;
|
||||
case 'y':
|
||||
match_y = 1;
|
||||
break;
|
||||
}
|
||||
for (row = 0; row < gd->sy; row++) {
|
||||
if (match_y) {
|
||||
if (y == 0)
|
||||
continue;
|
||||
if (row != y - 1)
|
||||
continue;
|
||||
}
|
||||
line = gd->linedata[gd->hsize + row].images;
|
||||
if (line == NULL)
|
||||
continue;
|
||||
TAILQ_FOREACH(span, &line->spans, line_entry) {
|
||||
if (span->placement != placement)
|
||||
continue;
|
||||
if (span->x >= gd->sx)
|
||||
continue;
|
||||
if (match_x) {
|
||||
if (x == 0)
|
||||
continue;
|
||||
if (x > gd->sx)
|
||||
continue;
|
||||
if (x - 1 < span->x)
|
||||
continue;
|
||||
if (x - 1 >= span->x + span->sx)
|
||||
continue;
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Clear ordinary Kitty placements selected by a graphics delete command. */
|
||||
void
|
||||
image_clear_kitty(struct screen_write_ctx *ctx,
|
||||
const struct kitty_parse_result *result)
|
||||
{
|
||||
struct grid *gd = ctx->s->grid;
|
||||
struct image_store *store = gd->images;
|
||||
struct image_placement *placement, *next;
|
||||
struct kitty_parse_result selection = *result;
|
||||
char how = result->delete;
|
||||
int matched;
|
||||
u_int placement_id = result->placement_id;
|
||||
|
||||
if (store == NULL)
|
||||
return;
|
||||
if (how >= 'A') {
|
||||
if (how <= 'Z')
|
||||
how += 'a' - 'A';
|
||||
}
|
||||
if (how == 'c') {
|
||||
selection.x = ctx->s->cx + 1;
|
||||
selection.y = ctx->s->cy + 1;
|
||||
}
|
||||
TAILQ_FOREACH_SAFE(placement, &store->placements, entry, next) {
|
||||
if (placement->input != IMAGE_INPUT_KITTY)
|
||||
continue;
|
||||
if (placement->placeholder)
|
||||
continue;
|
||||
matched = 0;
|
||||
switch (how) {
|
||||
case 'a': case 'A':
|
||||
case 'i':
|
||||
if (placement->app_image_id != result->image_id)
|
||||
break;
|
||||
if (placement_id != 0) {
|
||||
if (placement->app_placement_id != placement_id)
|
||||
break;
|
||||
}
|
||||
matched = 1;
|
||||
break;
|
||||
case 'i':
|
||||
matched = (placement->app_image_id == image_id &&
|
||||
(placement_id == 0 ||
|
||||
placement->app_placement_id == placement_id));
|
||||
case 'r':
|
||||
if (placement->app_image_id < result->x)
|
||||
break;
|
||||
if (placement->app_image_id > result->y)
|
||||
break;
|
||||
matched = 1;
|
||||
break;
|
||||
case 'I':
|
||||
matched = (placement->app_image_id == image_id);
|
||||
case 'z':
|
||||
if (placement->z == result->z)
|
||||
matched = 1;
|
||||
break;
|
||||
case 'z': case 'Z':
|
||||
matched = (placement->z == z);
|
||||
case 'q':
|
||||
if (placement->z != result->z)
|
||||
break;
|
||||
/* FALLTHROUGH */
|
||||
case 'a': case 'c': case 'p': case 'x': case 'y':
|
||||
matched = image_placement_intersects(gd, placement,
|
||||
&selection);
|
||||
break;
|
||||
}
|
||||
if (matched)
|
||||
@@ -1347,6 +1536,8 @@ image_cell_has_text(struct grid *gd, u_int x, u_int y)
|
||||
struct grid_cell gc;
|
||||
|
||||
grid_view_get_cell(gd, x, y, &gc);
|
||||
if (kitty_cell_is_placeholder(&gc))
|
||||
return (0);
|
||||
if (gc.data.size != 1 || gc.data.data[0] != ' ')
|
||||
return (1);
|
||||
return (gc.attr != 0);
|
||||
@@ -1561,6 +1752,8 @@ image_grid_resize_width(struct grid *gd, u_int new_sx)
|
||||
|
||||
for (i = 0; i < nseen; i++) {
|
||||
placement = seen[i];
|
||||
if (placement->placeholder)
|
||||
continue;
|
||||
|
||||
cx = end_x = source_y = 0;
|
||||
found = 0;
|
||||
|
||||
49
input.c
49
input.c
@@ -2805,9 +2805,11 @@ input_enter_apc(struct input_ctx *ictx)
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Reply to a Kitty graphics command with its image and placement IDs. */
|
||||
static void
|
||||
input_reply_kitty(struct input_ctx *ictx, struct kitty_parse_result *result,
|
||||
const char *message)
|
||||
input_reply_kitty(struct input_ctx *ictx,
|
||||
const struct kitty_parse_result *result, const char *message)
|
||||
{
|
||||
char ids[96];
|
||||
|
||||
if (result->quiet >= 2)
|
||||
return;
|
||||
if (result->quiet == 1) {
|
||||
@@ -2815,15 +2817,26 @@ input_reply_kitty(struct input_ctx *ictx, struct kitty_parse_result *result,
|
||||
return;
|
||||
}
|
||||
if (result->image_id == 0) {
|
||||
if (result->action != 'q')
|
||||
return;
|
||||
if (result->image_number == 0) {
|
||||
if (result->action != 'q')
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (result->image_number != 0) {
|
||||
if (result->image_id != 0) {
|
||||
xsnprintf(ids, sizeof ids, "i=%u,I=%u",
|
||||
result->image_id, result->image_number);
|
||||
} else
|
||||
xsnprintf(ids, sizeof ids, "I=%u",
|
||||
result->image_number);
|
||||
} else {
|
||||
xsnprintf(ids, sizeof ids, "i=%u", result->image_id);
|
||||
}
|
||||
if (result->placement_id != 0) {
|
||||
input_reply(ictx, 0, "\033_Gi=%u,p=%u;%s\033\\",
|
||||
result->image_id, result->placement_id, message);
|
||||
input_reply(ictx, 0, "\033_G%s,p=%u;%s\033\\", ids,
|
||||
result->placement_id, message);
|
||||
} else {
|
||||
input_reply(ictx, 0, "\033_Gi=%u;%s\033\\", result->image_id,
|
||||
message);
|
||||
input_reply(ictx, 0, "\033_G%s;%s\033\\", ids, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2845,23 +2858,20 @@ input_handle_kitty(struct input_ctx *ictx, const u_char *buf, size_t len)
|
||||
if (result.status != KITTY_PARSE_OK) {
|
||||
if (result.status == KITTY_PARSE_MISSING)
|
||||
input_reply_kitty(ictx, &result, "ENOENT");
|
||||
else if (result.status == KITTY_PARSE_UNSUPPORTED)
|
||||
input_reply_kitty(ictx, &result, "ENOTSUP");
|
||||
else
|
||||
input_reply_kitty(ictx, &result, "EINVAL");
|
||||
return (1);
|
||||
}
|
||||
if (result.replace_id != 0)
|
||||
image_clear(sctx, result.replace_id);
|
||||
if (im != NULL) {
|
||||
if (result.action == 'd')
|
||||
image_clear_kitty(sctx, result.delete, result.image_id,
|
||||
result.placement_id, result.z);
|
||||
else
|
||||
image_write_kitty(sctx, im, ictx->cell.cell.bg,
|
||||
result.image_id, result.placement_id, result.z);
|
||||
if (result.action == 'd') {
|
||||
kitty_delete_images(ictx->kitty_state, sctx, &result);
|
||||
} else if (im != NULL) {
|
||||
image_write_kitty(sctx, im, ictx->cell.cell.bg,
|
||||
result.image_id, result.placement_id, result.z);
|
||||
image_free(image_get_id(im));
|
||||
} else if (result.action == 'd') {
|
||||
image_clear_kitty(sctx, result.delete, result.image_id,
|
||||
result.placement_id, result.z);
|
||||
}
|
||||
if (result.action != 'd')
|
||||
input_reply_kitty(ictx, &result, "OK");
|
||||
@@ -2983,9 +2993,10 @@ input_top_bit_set(struct input_ctx *ictx)
|
||||
if (sctx->s->cx != 0) {
|
||||
x = sctx->s->cx - 1; /* cx-1 is the cell just written. */
|
||||
grid_view_get_cell(sctx->s->grid, x, sctx->s->cy, &gc);
|
||||
image_grid_remove_overwritten_spans(sctx->s->grid, x,
|
||||
sctx->s->grid->hsize + sctx->s->cy, 1, 1);
|
||||
if (kitty_placeholder_to_image(ictx->kitty_state,
|
||||
sctx->s->grid, &gc, x, sctx->s->cy, &placeholder)) {
|
||||
grid_view_set_cell(sctx->s->grid, x, sctx->s->cy, &gc);
|
||||
image_place_cell_kitty(sctx, placeholder.image, x,
|
||||
sctx->s->cy, placeholder.source_x,
|
||||
placeholder.source_y, placeholder.image_id,
|
||||
|
||||
@@ -13,7 +13,7 @@ TMUX2="$TEST_TMUX -Lkitty-clear-outer$$ -f/dev/null"
|
||||
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" 0 1 15
|
||||
|
||||
$TMUX new-session -d -x 10 -y 4 "
|
||||
printf '\033_Ga=T,q=2,f=24,s=1,v=1,c=1,r=1,C=1;////\033\\'
|
||||
printf '\033_Ga=T,q=2,f=24,s=1,v=2,c=1,r=1,C=1;////////\033\\'
|
||||
read line
|
||||
printf '\033[H\033[2K'
|
||||
read line
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Kitty cursor movement, acknowledgements and query isolation.
|
||||
# Kitty graphics layout, deletion, placeholders and replies.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
@@ -14,6 +14,7 @@ command -v python3 >/dev/null || exit 0
|
||||
python3 - <<'PY'
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
@@ -24,6 +25,8 @@ import zlib
|
||||
|
||||
tmux = [os.environ['TEST_TMUX'], '-u', '-Limage-protocol' + str(os.getpid()),
|
||||
'-f/dev/null']
|
||||
outer = [os.environ['TEST_TMUX'], '-u', '-Limage-protocol-outer' + str(os.getpid()),
|
||||
'-f/dev/null']
|
||||
|
||||
def run(*args):
|
||||
return subprocess.check_output(tmux + list(args), text=True).strip()
|
||||
@@ -36,7 +39,8 @@ reader = '''
|
||||
import json, os, select, sys, termios, time, tty
|
||||
command, output = sys.argv[1:]
|
||||
tty.setraw(0)
|
||||
os.write(1, command.encode() + b'\\x1b[c')
|
||||
with open(command, 'rb') as f:
|
||||
os.write(1, f.read() + b'\\x1b[c')
|
||||
reply = b''
|
||||
deadline = time.monotonic() + 3
|
||||
while time.monotonic() < deadline:
|
||||
@@ -51,10 +55,12 @@ with open(output, 'w') as f:
|
||||
time.sleep(30)
|
||||
'''
|
||||
|
||||
def check(name, command, cursor, expected='', text=None):
|
||||
def check(name, command, cursor, expected='', text=None, render=None, resize=None):
|
||||
output = directory / name
|
||||
command_file = directory / (name + '.input')
|
||||
command_file.write_text(command, encoding='utf-8')
|
||||
pane_command = 'python3 ' + shlex.quote(str(helper)) + ' ' + \
|
||||
shlex.quote(command) + ' ' + shlex.quote(str(output))
|
||||
shlex.quote(str(command_file)) + ' ' + shlex.quote(str(output))
|
||||
pane = run('new-window', '-d', '-P', '-F', '#{pane_id}', pane_command)
|
||||
try:
|
||||
deadline = time.monotonic() + 5
|
||||
@@ -64,11 +70,29 @@ def check(name, command, cursor, expected='', text=None):
|
||||
time.sleep(0.05)
|
||||
reply = json.loads(output.read_text())
|
||||
assert reply == expected, (name, 'reply', repr(reply), repr(expected))
|
||||
if resize is not None:
|
||||
run('resize-window', '-t', pane, '-x', str(resize[0]), '-y', str(resize[1]))
|
||||
actual = run('display-message', '-pt', pane, '#{cursor_x},#{cursor_y}')
|
||||
assert actual == cursor, (name, 'cursor', actual, cursor)
|
||||
if text is not None:
|
||||
actual = run('capture-pane', '-pt', pane, '-S0', '-E0')
|
||||
assert actual == text, (name, 'text', actual, text)
|
||||
if render is not None:
|
||||
run('select-window', '-t', pane)
|
||||
attach = shlex.join(tmux + ['attach-session'])
|
||||
client = subprocess.check_output(outer + ['new-window', '-d', '-P',
|
||||
'-F', '#{pane_id}', attach], text=True).strip()
|
||||
try:
|
||||
deadline = time.monotonic() + 3
|
||||
while time.monotonic() < deadline:
|
||||
actual = subprocess.check_output(outer + ['capture-pane', '-pe',
|
||||
'-t', client, '-S0', '-E0'], text=True).rstrip('\n')
|
||||
if re.search(render, actual):
|
||||
break
|
||||
time.sleep(0.05)
|
||||
assert re.search(render, actual), (name, 'render', repr(actual), render)
|
||||
finally:
|
||||
subprocess.run(outer + ['kill-window', '-t', client], check=True)
|
||||
finally:
|
||||
run('kill-window', '-t', pane)
|
||||
|
||||
@@ -80,6 +104,12 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
||||
run('new-session', '-d', '-x', '40', '-y', '12')
|
||||
if run('display-message', '-p', '#{image_support}') == '0':
|
||||
raise SystemExit(0)
|
||||
run('set', '-g', 'status', 'off')
|
||||
run('set', '-as', 'terminal-features', ',*:sixel@')
|
||||
run('set', '-as', 'terminal-features', ',*:kitty@')
|
||||
run('set', '-as', 'terminal-features', ',*:RGB')
|
||||
subprocess.run(outer + ['new-session', '-d', '-x', '40', '-y', '12'], check=True)
|
||||
subprocess.run(outer + ['set', '-g', 'status', 'off'], check=True)
|
||||
origin = '\033[3;6H' # Column 6, row 3 (zero-based 5,2).
|
||||
pixel = '/wAA/w=='
|
||||
placement = 'a=T,q=2,f=32,s=1,v=1,c=3,r=2'
|
||||
@@ -123,8 +153,125 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
||||
# valid for the original three-column placement, but not the query.
|
||||
check('query-virtual', graphics(placement + ',U=1,i=7', pixel) +
|
||||
graphics('a=q,q=2,U=1,i=7,f=32,s=1,v=1,c=1,r=1', pixel) +
|
||||
'\033[38;2;0;0;7m\U0010eeee\u0305\u030e', '1,0', text='')
|
||||
'\033[38;2;0;0;7m\U0010eeee\u0305\u030e', '1,0',
|
||||
text='\U0010eeee\u0305\u030e')
|
||||
# Uppercase deletion must leave other numbered and unnumbered placements.
|
||||
transmit = graphics('a=t,q=2,i=7,f=32,s=1,v=1', pixel)
|
||||
check('delete-one-placement', transmit + graphics('a=p,q=2,i=7,p=1,C=1') +
|
||||
graphics('a=p,q=2,i=7,p=2,C=1') + graphics('a=d,d=I,q=2,i=7,p=1') +
|
||||
graphics('a=p,i=7,p=2,C=1'), '0,0', graphics('i=7,p=2', 'OK'))
|
||||
check('delete-last-placement', transmit + graphics('a=p,q=2,i=7,p=1,C=1') +
|
||||
graphics('a=d,d=I,q=2,i=7,p=1') + graphics('a=p,i=7'), '0,0',
|
||||
graphics('i=7', 'ENOENT'))
|
||||
check('delete-retain-unnumbered', transmit + graphics('a=p,q=2,i=7,p=1,C=1') +
|
||||
graphics('a=p,q=2,i=7,C=1') + graphics('a=d,d=I,q=2,i=7,p=1') +
|
||||
graphics('a=p,i=7,C=1'), '0,0', graphics('i=7', 'OK'))
|
||||
check('delete-soft', transmit + graphics('a=p,q=2,i=7,p=1,C=1') +
|
||||
graphics('a=d,d=i,q=2,i=7,p=1') + graphics('a=p,i=7,C=1'),
|
||||
'0,0', graphics('i=7', 'OK'))
|
||||
for selector in ['A', 'Z']:
|
||||
check('delete-hard-' + selector, transmit + graphics('a=p,q=2,i=7,C=1,z=9') +
|
||||
graphics('a=d,d=%s,z=9,q=2' % selector) + graphics('a=p,i=7'),
|
||||
'0,0', graphics('i=7', 'ENOENT'))
|
||||
# A fully historical placement retains its source after visible deletion.
|
||||
check('delete-history', transmit + graphics('a=p,q=2,i=7,C=1') + '\n' * 13 +
|
||||
graphics('a=d,d=A,q=2') + graphics('a=p,i=7,C=1'), '0,11',
|
||||
graphics('i=7', 'OK'))
|
||||
# Delete controls must not inherit an unfinished upload's image ID.
|
||||
check('delete-aborts-upload', transmit +
|
||||
graphics('a=t,q=2,i=7,f=32,s=1,v=1,m=1', '/wAA') +
|
||||
graphics('a=d,d=I') + graphics('a=p,i=7,C=1'), '0,0',
|
||||
graphics('i=7', 'OK'))
|
||||
for selector, coordinates in [('C', ''), ('P', ',x=1,y=1'),
|
||||
('Q', ',x=1,y=1,z=9'), ('X', ',x=1'),
|
||||
('Y', ',y=1'), ('R', ',x=7,y=7')]:
|
||||
check('delete-selector-' + selector, transmit +
|
||||
graphics('a=p,q=2,i=7,C=1,z=9') +
|
||||
graphics('a=d,d=%s,q=2%s' % (selector, coordinates)) +
|
||||
graphics('a=p,i=7'), '0,0', graphics('i=7', 'ENOENT'))
|
||||
check('delete-misses', transmit + graphics('a=p,q=2,i=7,C=1,z=9') +
|
||||
graphics('a=d,d=Q,x=1,y=1,z=8,q=2') +
|
||||
graphics('a=d,d=P,x=2,y=1,q=2') +
|
||||
graphics('a=p,i=7,C=1'), '0,0', graphics('i=7', 'OK'))
|
||||
check('delete-range-unused-source', transmit + graphics('a=d,d=R,x=7,y=7,p=99') +
|
||||
graphics('a=p,i=7'), '0,0', graphics('i=7', 'ENOENT'))
|
||||
white = base64.b64encode(b'\xff' * (24 * 32 * 4)).decode()
|
||||
virtual = graphics('a=T,q=2,U=1,i=7,p=1,f=32,s=24,v=32,c=3,r=2', white)
|
||||
ph = '\U0010eeee'
|
||||
colours = '\033[38;2;0;0;7m\033[58;2;0;0;1m'
|
||||
check('multiple-virtual', virtual + graphics('a=p,q=2,U=1,i=7,p=2,c=1,r=1') +
|
||||
colours + ph + '\u0305\u030e', '1,0', text=ph + '\u0305\u030e',
|
||||
render=r'255;255;255m')
|
||||
high_virtual = graphics('a=T,q=2,U=1,i=16777223,f=32,s=24,v=32,c=3,r=2', white)
|
||||
check('inherit-high-byte', high_virtual + '\033[38;2;0;0;7m' +
|
||||
ph + '\u0305\u0305\u030d' + ph, '2,0',
|
||||
text=ph + '\u0305\u0305\u030d' + ph, render=r'48;2;255;255;255m {2}')
|
||||
check('inherit-high-byte-row', high_virtual + '\033[38;2;0;0;7m' +
|
||||
ph + '\u0305\u0305\u030d' + ph + '\u0305', '2,0',
|
||||
render=r'48;2;255;255;255m {2}')
|
||||
check('inherit-high-byte-column', high_virtual + '\033[38;2;0;0;7m' +
|
||||
ph + '\u0305\u0305\u030d' + ph + '\u0305\u030d', '2,0',
|
||||
render=r'48;2;255;255;255m {2}')
|
||||
for name, change, diacritics in [
|
||||
('colour', '\033[38;2;0;0;8m', ''),
|
||||
('placement', '\033[58;2;0;0;1m', ''),
|
||||
('row', '', '\u030d'),
|
||||
('column', '', '\u0305\u030e')]:
|
||||
check('inherit-mismatch-' + name, high_virtual + '\033[38;2;0;0;7m' +
|
||||
ph + '\u0305\u0305\u030d' + change + ph + diacritics, '2,0',
|
||||
render=ph)
|
||||
check('palette-id', virtual + '\033[38;5;7m\033[58;5;1m' + ph, '1,0',
|
||||
render=r'255;255;255m')
|
||||
check('negative-virtual', virtual + graphics('a=p,q=2,U=1,i=7,p=1,c=3,r=2,z=-1') +
|
||||
colours + ph, '1,0', render=r'255;255;255m')
|
||||
check('placeholder-resize', virtual + colours + ph, '1,0',
|
||||
render=r'48;2;255;255;255m(?:\x1b\[[0-9;]+m)* \x1b\[(?:0|39)m',
|
||||
resize=(50, 12))
|
||||
for selector in ['a', 'z']:
|
||||
check('virtual-survives-' + selector, virtual + colours + ph +
|
||||
graphics('a=d,d=%s,q=2' % selector), '1,0', render=r'255;255;255m')
|
||||
# Deleting a prototype leaves its existing text-backed display intact.
|
||||
check('virtual-delete-display', virtual + colours + ph +
|
||||
graphics('a=d,d=I,i=7,p=1,q=2'), '1,0', render=r'255;255;255m')
|
||||
check('virtual-delete-prototype', virtual + graphics('a=d,d=i,i=7,p=1,q=2') +
|
||||
colours + ph, '1,0', render=ph)
|
||||
check('virtual-replace-display', virtual + colours + ph +
|
||||
graphics('a=p,i=7,p=1,q=2,C=1,c=3,r=2'), '1,0',
|
||||
render=r'^(?:\x1b\[[0-9;]+m)*\x1b\[48;2;255;255;255m(?:\x1b\[[0-9;]+m)* ')
|
||||
check('placeholder-overwrite', virtual + colours + ph + '\033[HX', '1,0',
|
||||
text='X', render=r'X')
|
||||
check('placeholder-erase', virtual + colours + ph + '\033[H\033[2K', '0,0',
|
||||
text='', render=r'^\s*$')
|
||||
white_pixel = '/////w=='
|
||||
check('letterbox', graphics('a=T,q=2,f=32,s=1,v=1,c=2,r=2,C=1', white_pixel),
|
||||
'0,0', render='▄▄')
|
||||
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',
|
||||
offset_source), '0,0', render=r'12[78];12[78];12[78]m')
|
||||
check('offset-clamp', graphics('a=T,q=2,f=32,s=1,v=1,X=4294967295,C=1',
|
||||
white_pixel), '0,0')
|
||||
check('oversized-placement', transmit + graphics('a=p,i=7,c=65534,r=32767'),
|
||||
'0,0', graphics('i=7', 'EINVAL'))
|
||||
large_rgb = base64.b64encode(zlib.compress(b'\0' * (4096 * 4097 * 3))).decode()
|
||||
check('oversized-rgb', graphics('a=t,i=7,f=24,s=4096,v=4097,o=z', large_rgb),
|
||||
'0,0', graphics('i=7', 'EINVAL'))
|
||||
for action in ['f', 'a', 'c']:
|
||||
check('unsupported-action-' + action, graphics('a=%s,i=7' % action),
|
||||
'0,0', graphics('i=7', 'ENOTSUP'))
|
||||
for key in ['P', 'Q', 'H', 'V']:
|
||||
check('unsupported-key-' + key, graphics('a=p,i=7,%s=1' % key),
|
||||
'0,0', graphics('i=7', 'ENOTSUP'))
|
||||
check('relative-defaults', transmit + graphics('a=p,i=7,P=0,Q=0,H=0,V=0,C=1'),
|
||||
'0,0', graphics('i=7', 'OK'))
|
||||
check('unsupported-number', graphics('a=t,I=7,f=32,s=1,v=1', pixel),
|
||||
'0,0', graphics('I=7', 'ENOTSUP'))
|
||||
check('id-and-number', graphics('a=t,i=7,I=8,f=32,s=1,v=1', pixel),
|
||||
'0,0', graphics('i=7,I=8', 'EINVAL'))
|
||||
check('unknown-extension', graphics('a=T,q=2,f=32,s=1,v=1,c=3,r=2,k=9', pixel),
|
||||
'3,2')
|
||||
finally:
|
||||
subprocess.run(tmux + ['kill-server'], stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL)
|
||||
subprocess.run(outer + ['kill-server'], stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL)
|
||||
PY
|
||||
|
||||
@@ -14,13 +14,14 @@ TMP=$(mktemp)
|
||||
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
|
||||
|
||||
# White placements stay behind the final red placement.
|
||||
# An 8:1 source fills 16 columns by one row without letterboxing.
|
||||
$TMUX2 new-session -d -x 8 -y 4 "
|
||||
i=0
|
||||
while [ \$i -lt 64 ]; do
|
||||
printf '\\033_Ga=T,q=2,C=1,f=32,s=1,v=1,c=16,r=1;/////w==\\033\\\\'
|
||||
printf '\\033_Ga=T,q=2,C=1,f=32,s=8,v=1,c=16,r=1;//////////////////////////////////////////8=\\033\\\\'
|
||||
i=\$((i + 1))
|
||||
done
|
||||
printf '\\033_Ga=T,q=2,C=1,f=32,s=1,v=1,c=16,r=1;/wAA/w==\\033\\\\'
|
||||
printf '\\033_Ga=T,q=2,C=1,f=32,s=8,v=1,c=16,r=1;/wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP8=\\033\\\\'
|
||||
sleep 30" || exit 1
|
||||
[ "$($TMUX2 display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
$TMUX2 set -g status off || exit 1
|
||||
|
||||
@@ -61,7 +61,7 @@ $TMUX send-keys -X cancel || exit 1
|
||||
# Ordinary text updates the image underlay through the normal grid write path.
|
||||
# Deleting the image reveals the newly written text.
|
||||
$TMUX new-window -d "
|
||||
printf '\033_Ga=T,q=2,f=32,s=2,v=2,c=2,r=2,i=8;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\033_Ga=T,q=2,f=32,s=2,v=4,c=2,r=2,i=8;/wAA/wD/AP//AAD/AP8A/wAA////////AAD///////8=\033\\'
|
||||
printf '\033[HXY'
|
||||
printf '\033_Ga=d,d=i,q=2,i=8\033\\'
|
||||
sleep 10"
|
||||
@@ -148,11 +148,12 @@ grep -q '^#=' $TMP || exit 1
|
||||
|
||||
# A retained Kitty image may be placed repeatedly using source rectangles.
|
||||
# Crop the green and white right column from a red/green/blue/white image.
|
||||
# Each colour spans two source rows to match the cell aspect ratio.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 10 -y 4 "
|
||||
printf '\033_Ga=t,q=2,f=32,s=2,v=2,i=10;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\033_Ga=p,q=2,i=10,x=1,y=0,w=1,h=2,c=1,r=2\033\\'
|
||||
printf '\033_Ga=t,q=2,f=32,s=2,v=4,i=10;/wAA/wD/AP//AAD/AP8A/wAA////////AAD///////8=\033\\'
|
||||
printf '\033_Ga=p,q=2,i=10,x=1,y=0,w=1,h=4,c=1,r=2\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX new-session -d -x 10 -y 4 || exit 1
|
||||
@@ -169,7 +170,7 @@ $TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
# row off screen.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033[2;3H'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=8,c=1,r=4;AAAA/wAAAP9VVVX/VVVV/6qqqv+qqqr///////////8=\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:1 || exit 1
|
||||
sleep 1
|
||||
@@ -183,7 +184,7 @@ $TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
# the bottom three source rows remain visible and the cursor is on the last row.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033[3;1H'
|
||||
printf '\033_Ga=T,q=2,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\'
|
||||
printf '\033_Ga=T,q=2,f=32,s=1,v=8,c=1,r=4;AAAA/wAAAP9VVVX/VVVV/6qqqv+qqqr///////////8=\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:2 || exit 1
|
||||
sleep 1
|
||||
@@ -197,7 +198,7 @@ $TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
# reflow. The ten-column rows are clipped to five columns, not split into four
|
||||
# wrapped rows.
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=2,c=10,r=2;/wAA//////8=\033\\'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=5,v=2,c=10,r=2;/wAA//8AAP//AAD//wAA//8AAP///////////////////////////w==\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:3 || exit 1
|
||||
sleep 1
|
||||
@@ -271,7 +272,7 @@ sleep 1
|
||||
# the image. Deleting the placement afterwards reveals the updated text.
|
||||
TEXT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf 'test\r'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=2,v=2,c=2,r=2,i=14,p=7;/wAA/wD/AP8AAP///////w==\033\\'
|
||||
printf '\033_Ga=T,q=2,C=1,f=32,s=2,v=4,c=2,r=2,i=14,p=7;/wAA/wD/AP//AAD/AP8A/wAA////////AAD///////8=\033\\'
|
||||
printf '\r'
|
||||
printf 'test\n'
|
||||
printf 'test\n'
|
||||
@@ -295,8 +296,8 @@ $TMUX capture-pane -pS0 -E1 >$TMP || exit 1
|
||||
# negative z-indexes cover backgrounds but not text, and very negative ones
|
||||
# remain below the background.
|
||||
Z_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=21;/wAA/w==\033\\'
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=22;/////w==\033\\'
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=2,i=21;/wAA//8AAP8=\033\\'
|
||||
printf '\033_Ga=t,q=2,f=32,s=1,v=2,i=22;//////////8=\033\\'
|
||||
printf 'X\r'
|
||||
printf '\033_Ga=p,q=2,C=1,i=21,p=1,z=2,c=1,r=1\033\\'
|
||||
printf '\033_Ga=p,q=2,C=1,i=22,p=2,z=1,c=1,r=1\033\\'
|
||||
|
||||
@@ -404,8 +404,8 @@ screen_write_image_damage(struct screen_write_ctx *ctx, u_int x, u_int y,
|
||||
{
|
||||
image_redraw_area(ctx, x, y, sx, sy);
|
||||
if (ctx->flags & SCREEN_WRITE_INPUT)
|
||||
image_grid_remove_sixel_spans(ctx->s->grid, x, ctx->s->grid->hsize + y,
|
||||
sx, sy);
|
||||
image_grid_remove_overwritten_spans(ctx->s->grid, x,
|
||||
ctx->s->grid->hsize + y, sx, sy);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1492,7 +1492,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_all(ctx);
|
||||
if (ctx->flags & SCREEN_WRITE_INPUT)
|
||||
image_grid_remove_sixel_spans(s->grid, 0, s->grid->hsize,
|
||||
image_grid_remove_overwritten_spans(s->grid, 0, s->grid->hsize,
|
||||
screen_size_x(s), screen_size_y(s));
|
||||
#endif
|
||||
|
||||
|
||||
40
tmux.h
40
tmux.h
@@ -1107,18 +1107,37 @@ struct image {
|
||||
RB_HEAD(images, image);
|
||||
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
|
||||
|
||||
struct image_view {
|
||||
u_int x;
|
||||
u_int y;
|
||||
u_int width;
|
||||
u_int height;
|
||||
u_int scaled_width;
|
||||
u_int scaled_height;
|
||||
u_int canvas_width;
|
||||
u_int canvas_height;
|
||||
u_int sx;
|
||||
u_int sy;
|
||||
u_int x_offset;
|
||||
u_int y_offset;
|
||||
};
|
||||
|
||||
enum kitty_parse_status {
|
||||
KITTY_PARSE_ERROR = -1,
|
||||
KITTY_PARSE_OK,
|
||||
KITTY_PARSE_MORE,
|
||||
KITTY_PARSE_MISSING
|
||||
KITTY_PARSE_MISSING,
|
||||
KITTY_PARSE_UNSUPPORTED
|
||||
};
|
||||
|
||||
struct kitty_parse_result {
|
||||
u_int image_id;
|
||||
u_int image_number;
|
||||
u_int replace_id;
|
||||
u_int placement_id;
|
||||
u_int quiet;
|
||||
u_int x;
|
||||
u_int y;
|
||||
int32_t z;
|
||||
char action;
|
||||
char delete;
|
||||
@@ -4331,8 +4350,7 @@ char *regsub(const char *, const char *, const char *, int);
|
||||
|
||||
struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int,
|
||||
u_char *);
|
||||
struct image *image_create_view(struct image *, u_int, u_int, u_int,
|
||||
u_int, u_int, u_int, u_int, u_int, u_int, u_int);
|
||||
struct image *image_create_view(struct image *, const struct image_view *);
|
||||
struct image *image_find(u_int);
|
||||
u_int image_get_id(const struct image *);
|
||||
void image_get_size(const struct image *, u_int *, u_int *);
|
||||
@@ -4379,10 +4397,11 @@ void image_rect_get_coords(const struct image_rect *,
|
||||
u_int *, u_int *, u_int *, u_int *, u_int *, u_int *);
|
||||
int32_t image_rect_get_z(const struct image_rect *);
|
||||
void image_clear(struct screen_write_ctx *, u_int);
|
||||
void image_clear_kitty(struct screen_write_ctx *, char, u_int,
|
||||
u_int, int32_t);
|
||||
void image_grid_remove_sixel_spans(struct grid *, u_int, u_int,
|
||||
u_int, u_int);
|
||||
void image_clear_kitty(struct screen_write_ctx *,
|
||||
const struct kitty_parse_result *);
|
||||
int image_grid_has_image(struct grid *, u_int);
|
||||
void image_grid_remove_overwritten_spans(struct grid *, u_int,
|
||||
u_int, u_int, u_int);
|
||||
void image_grid_free_line(struct grid *, struct grid_line *);
|
||||
void image_grid_free(struct grid *);
|
||||
void image_grid_move_cells(struct grid *, u_int, u_int, u_int,
|
||||
@@ -4395,8 +4414,8 @@ void image_grid_resize_width(struct grid *, u_int);
|
||||
int image_grid_line_has_images(const struct grid_line *);
|
||||
int image_grid_area_has_images(struct grid *, u_int, u_int, u_int,
|
||||
u_int);
|
||||
int image_grid_get_source(struct grid *, u_int, u_int,
|
||||
struct image *, u_int *, u_int *);
|
||||
int image_grid_get_placeholder(struct grid *, u_int, u_int,
|
||||
struct kitty_placeholder *);
|
||||
void image_place_cell_kitty(struct screen_write_ctx *, struct image *,
|
||||
u_int, u_int, u_int, u_int, u_int, u_int, int32_t);
|
||||
/* image-kitty.c */
|
||||
@@ -4405,6 +4424,9 @@ struct image *kitty_parse_image(void **, const u_char *, size_t, u_int,
|
||||
int kitty_placeholder_to_image(void *, struct grid *,
|
||||
struct grid_cell *, u_int, u_int,
|
||||
struct kitty_placeholder *);
|
||||
int kitty_cell_is_placeholder(const struct grid_cell *);
|
||||
void kitty_delete_images(void *, struct screen_write_ctx *,
|
||||
const struct kitty_parse_result *);
|
||||
void kitty_free_state(void *);
|
||||
void kitty_draw_rect(struct tty *,
|
||||
const struct image_rect *);
|
||||
|
||||
@@ -124,6 +124,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
||||
struct grid_cell gc, ngc, last;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell image_gc;
|
||||
struct kitty_placeholder placeholder;
|
||||
int image_status;
|
||||
#endif
|
||||
struct grid_line *gl;
|
||||
@@ -270,6 +271,14 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
||||
} else {
|
||||
/* Get the current cell. */
|
||||
grid_view_get_cell(gd, px + i, py, &gc);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (kitty_cell_is_placeholder(&gc)) {
|
||||
if (image_grid_get_placeholder(gd,
|
||||
px + i, gd->hsize + py,
|
||||
&placeholder))
|
||||
utf8_set(&gc.data, ' ');
|
||||
}
|
||||
#endif
|
||||
gcp = &gc;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (image_get_fallback_at(tty, s, px + i, py, &gc,
|
||||
|
||||
18
tty.c
18
tty.c
@@ -1935,6 +1935,22 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx)
|
||||
void
|
||||
tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
const struct grid_cell *cell = ctx->cell;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell placeholder_cell;
|
||||
struct kitty_placeholder placeholder;
|
||||
|
||||
if (kitty_cell_is_placeholder(cell)) {
|
||||
if (image_grid_get_placeholder(ctx->s->grid, ctx->ocx,
|
||||
ctx->s->grid->hsize + ctx->ocy, &placeholder)) {
|
||||
memcpy(&placeholder_cell, cell,
|
||||
sizeof placeholder_cell);
|
||||
utf8_set(&placeholder_cell.data, ' ');
|
||||
cell = &placeholder_cell;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, 1, 1))
|
||||
return;
|
||||
|
||||
@@ -1948,7 +1964,7 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
|
||||
tty_invalidate(tty);
|
||||
tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy);
|
||||
|
||||
tty_cell(tty, ctx->cell, &ctx->style_ctx);
|
||||
tty_cell(tty, cell, &ctx->style_ctx);
|
||||
|
||||
if (ctx->flags & TTY_CTX_CELL_INVALIDATE)
|
||||
tty_invalidate(tty);
|
||||
|
||||
Reference in New Issue
Block a user