mirror of
https://github.com/tmux/tmux.git
synced 2026-10-16 16:44:30 +00:00
Add a limit on image count and size.
This commit is contained in:
36
image-grid.c
36
image-grid.c
@@ -60,7 +60,12 @@ TAILQ_HEAD(image_placements, image_placement);
|
|||||||
/* Placements owned by one grid. */
|
/* Placements owned by one grid. */
|
||||||
struct image_store {
|
struct image_store {
|
||||||
struct image_placements placements;
|
struct image_placements placements;
|
||||||
|
TAILQ_ENTRY(image_store) entry;
|
||||||
};
|
};
|
||||||
|
TAILQ_HEAD(image_stores, image_store);
|
||||||
|
|
||||||
|
/* All current placements. */
|
||||||
|
static struct image_stores image_stores = TAILQ_HEAD_INITIALIZER(image_stores);
|
||||||
|
|
||||||
/* A shared placements copied when duplicating lines. */
|
/* A shared placements copied when duplicating lines. */
|
||||||
struct image_copy_map {
|
struct image_copy_map {
|
||||||
@@ -175,6 +180,7 @@ image_draw_span(struct image_backend *backend, struct tty *tty,
|
|||||||
struct image_rect rect;
|
struct image_rect rect;
|
||||||
|
|
||||||
rect.image = span->placement->image;
|
rect.image = span->placement->image;
|
||||||
|
image_touch(rect.image);
|
||||||
rect.src_x = span->source_x + start - span->x;
|
rect.src_x = span->source_x + start - span->x;
|
||||||
rect.src_y = span->source_y;
|
rect.src_y = span->source_y;
|
||||||
rect.sx = end - start;
|
rect.sx = end - start;
|
||||||
@@ -265,6 +271,7 @@ image_store_get(struct grid *gd)
|
|||||||
if (gd->images == NULL) {
|
if (gd->images == NULL) {
|
||||||
gd->images = xcalloc(1, sizeof *gd->images);
|
gd->images = xcalloc(1, sizeof *gd->images);
|
||||||
TAILQ_INIT(&gd->images->placements);
|
TAILQ_INIT(&gd->images->placements);
|
||||||
|
TAILQ_INSERT_TAIL(&image_stores, gd->images, entry);
|
||||||
}
|
}
|
||||||
return (gd->images);
|
return (gd->images);
|
||||||
}
|
}
|
||||||
@@ -293,6 +300,7 @@ image_placement_create(struct grid *gd, struct image *im)
|
|||||||
TAILQ_INIT(&plmt->spans);
|
TAILQ_INIT(&plmt->spans);
|
||||||
TAILQ_INSERT_TAIL(&store->placements, plmt, entry);
|
TAILQ_INSERT_TAIL(&store->placements, plmt, entry);
|
||||||
image_ref(im);
|
image_ref(im);
|
||||||
|
image_touch(im);
|
||||||
return (plmt);
|
return (plmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,6 +448,32 @@ image_grid_remove(struct grid *gd, u_int x, u_int y, u_int sx, u_int sy)
|
|||||||
image_store_prune(gd->images);
|
image_store_prune(gd->images);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Remove a placement and all of its spans. */
|
||||||
|
static void
|
||||||
|
image_remove_placement(struct image_placement *plmt)
|
||||||
|
{
|
||||||
|
struct image_span *span, *span1;
|
||||||
|
|
||||||
|
TAILQ_FOREACH_SAFE(span, &plmt->spans, placement_entry, span1)
|
||||||
|
image_span_free(span);
|
||||||
|
image_placement_free(plmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove every placement for an image. */
|
||||||
|
void
|
||||||
|
image_grid_remove_all(struct image *im)
|
||||||
|
{
|
||||||
|
struct image_store *store;
|
||||||
|
struct image_placement *plmt, *next;
|
||||||
|
|
||||||
|
TAILQ_FOREACH(store, &image_stores, entry) {
|
||||||
|
TAILQ_FOREACH_SAFE(plmt, &store->placements, entry, next) {
|
||||||
|
if (plmt->image == im)
|
||||||
|
image_remove_placement(plmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Free all spans on a grid line. */
|
/* Free all spans on a grid line. */
|
||||||
void
|
void
|
||||||
image_grid_free_line(struct grid *gd, struct grid_line *gl)
|
image_grid_free_line(struct grid *gd, struct grid_line *gl)
|
||||||
@@ -464,6 +498,7 @@ image_grid_free(struct grid *gd)
|
|||||||
{
|
{
|
||||||
if (gd->images != NULL) {
|
if (gd->images != NULL) {
|
||||||
image_store_prune(gd->images);
|
image_store_prune(gd->images);
|
||||||
|
TAILQ_REMOVE(&image_stores, gd->images, entry);
|
||||||
free(gd->images);
|
free(gd->images);
|
||||||
gd->images = NULL;
|
gd->images = NULL;
|
||||||
}
|
}
|
||||||
@@ -973,6 +1008,7 @@ image_grid_get_at(struct grid *gd, u_int x, u_int y, struct image **im,
|
|||||||
if (found == NULL)
|
if (found == NULL)
|
||||||
return (-1);
|
return (-1);
|
||||||
*im = found->placement->image;
|
*im = found->placement->image;
|
||||||
|
image_touch(*im);
|
||||||
*src_x = found->source_x + x - found->x;
|
*src_x = found->source_x + x - found->x;
|
||||||
*src_y = found->source_y;
|
*src_y = found->source_y;
|
||||||
return (1);
|
return (1);
|
||||||
|
|||||||
121
image.c
121
image.c
@@ -26,13 +26,19 @@
|
|||||||
|
|
||||||
#include "tmux.h"
|
#include "tmux.h"
|
||||||
|
|
||||||
|
/* Maximum number of images and total bytes. */
|
||||||
|
#define IMAGE_MAX_IMAGES 1000
|
||||||
|
#define IMAGE_MAX_BYTES (1024 * 1024 * 1024)
|
||||||
|
|
||||||
/* An image. */
|
/* An image. */
|
||||||
|
TAILQ_HEAD(image_list, image);
|
||||||
struct image {
|
struct image {
|
||||||
u_int id;
|
u_int id;
|
||||||
u_int references;
|
u_int references;
|
||||||
struct image_geom geom;
|
struct image_geom geom;
|
||||||
|
|
||||||
struct image *parent;
|
struct image *parent;
|
||||||
|
struct image_list children;
|
||||||
u_int order_id;
|
u_int order_id;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -46,10 +52,19 @@ struct image {
|
|||||||
u_char *pixels;
|
u_char *pixels;
|
||||||
|
|
||||||
RB_ENTRY(image) entry;
|
RB_ENTRY(image) entry;
|
||||||
|
TAILQ_ENTRY(image) all_entry;
|
||||||
|
TAILQ_ENTRY(image) child_entry;
|
||||||
};
|
};
|
||||||
RB_HEAD(images, image);
|
RB_HEAD(images, image);
|
||||||
static struct images images = RB_INITIALIZER(&images);
|
static struct images images = RB_INITIALIZER(&images);
|
||||||
|
|
||||||
|
/* All images, most recently used first. */
|
||||||
|
static struct image_list all_images = TAILQ_HEAD_INITIALIZER(all_images);
|
||||||
|
|
||||||
|
/* Number of images and size. */
|
||||||
|
static u_int image_count;
|
||||||
|
static size_t image_size;
|
||||||
|
|
||||||
/* Next image ID. */
|
/* Next image ID. */
|
||||||
static u_int image_next_id;
|
static u_int image_next_id;
|
||||||
|
|
||||||
@@ -65,6 +80,18 @@ image_cmp(struct image *a, struct image *b)
|
|||||||
}
|
}
|
||||||
RB_GENERATE_STATIC(images, image, entry, image_cmp);
|
RB_GENERATE_STATIC(images, image, entry, image_cmp);
|
||||||
|
|
||||||
|
/* Record use of an image and its parents. */
|
||||||
|
void
|
||||||
|
image_touch(struct image *im)
|
||||||
|
{
|
||||||
|
for (; im != NULL; im = im->parent) {
|
||||||
|
if (TAILQ_FIRST(&all_images) != im) {
|
||||||
|
TAILQ_REMOVE(&all_images, im, all_entry);
|
||||||
|
TAILQ_INSERT_HEAD(&all_images, im, all_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Find an image by its ID. */
|
/* Find an image by its ID. */
|
||||||
struct image *
|
struct image *
|
||||||
image_find(u_int id)
|
image_find(u_int id)
|
||||||
@@ -82,6 +109,76 @@ image_get_id(struct image *im)
|
|||||||
return (im->id);
|
return (im->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Remove an image and children. */
|
||||||
|
static void
|
||||||
|
image_remove_all(struct image *im)
|
||||||
|
{
|
||||||
|
struct image *child;
|
||||||
|
|
||||||
|
image_ref(im);
|
||||||
|
while ((child = TAILQ_FIRST(&im->children)) != NULL)
|
||||||
|
image_remove_all(child);
|
||||||
|
image_grid_remove_all(im);
|
||||||
|
image_free(im);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find the oldest image. */
|
||||||
|
static struct image *
|
||||||
|
image_find_oldest(struct image *keep, size_t size)
|
||||||
|
{
|
||||||
|
struct image *im, *parent;
|
||||||
|
|
||||||
|
TAILQ_FOREACH_REVERSE(im, &all_images, image_list, all_entry) {
|
||||||
|
if (image_size + size > IMAGE_MAX_BYTES && im->parent != NULL)
|
||||||
|
continue;
|
||||||
|
for (parent = keep; parent != NULL; parent = parent->parent) {
|
||||||
|
if (parent == im)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (parent == NULL)
|
||||||
|
return (im);
|
||||||
|
}
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Is there enough space for this image? */
|
||||||
|
static int
|
||||||
|
image_can_add(size_t size)
|
||||||
|
{
|
||||||
|
if (image_count >= IMAGE_MAX_IMAGES)
|
||||||
|
return (0);
|
||||||
|
if (image_size + size > IMAGE_MAX_BYTES)
|
||||||
|
return (0);
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check limits and remove images if needed. */
|
||||||
|
static int
|
||||||
|
image_check_limits(struct image *keep, size_t size)
|
||||||
|
{
|
||||||
|
struct image *oldest;
|
||||||
|
struct client *c;
|
||||||
|
|
||||||
|
if (size > IMAGE_MAX_BYTES)
|
||||||
|
return (-1);
|
||||||
|
if (image_can_add(size))
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
TAILQ_FOREACH(c, &clients, entry)
|
||||||
|
image_tty_free(&c->tty);
|
||||||
|
while (!image_can_add(size)) {
|
||||||
|
oldest = image_find_oldest(keep, size);
|
||||||
|
if (oldest == NULL)
|
||||||
|
break;
|
||||||
|
image_remove_all(oldest);
|
||||||
|
}
|
||||||
|
TAILQ_FOREACH(c, &clients, entry)
|
||||||
|
server_redraw_client(c);
|
||||||
|
if (!image_can_add(size))
|
||||||
|
return (-1);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
/* Allocate an image and assign a unique ID. */
|
/* Allocate an image and assign a unique ID. */
|
||||||
static struct image *
|
static struct image *
|
||||||
image_alloc(struct image_geom *ig, size_t stride, u_char *pixels)
|
image_alloc(struct image_geom *ig, size_t stride, u_char *pixels)
|
||||||
@@ -103,7 +200,9 @@ image_alloc(struct image_geom *ig, size_t stride, u_char *pixels)
|
|||||||
im->stride = stride;
|
im->stride = stride;
|
||||||
im->size = (ig->src_h - 1) * stride + ig->src_w * 4;
|
im->size = (ig->src_h - 1) * stride + ig->src_w * 4;
|
||||||
im->pixels = pixels;
|
im->pixels = pixels;
|
||||||
|
TAILQ_INIT(&im->children);
|
||||||
RB_INSERT(images, &images, im);
|
RB_INSERT(images, &images, im);
|
||||||
|
image_count++;
|
||||||
return (im);
|
return (im);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +221,7 @@ image_create_with_parent(struct image *parent, struct image_geom *ig,
|
|||||||
size_t stride, u_char *pixels)
|
size_t stride, u_char *pixels)
|
||||||
{
|
{
|
||||||
struct image *im;
|
struct image *im;
|
||||||
|
size_t size = 0;
|
||||||
|
|
||||||
if (ig->src_w == 0 || ig->src_h == 0)
|
if (ig->src_w == 0 || ig->src_h == 0)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
@@ -138,12 +238,22 @@ image_create_with_parent(struct image *parent, struct image_geom *ig,
|
|||||||
if (ig->scaled_width <= 0 || ig->scaled_height <= 0)
|
if (ig->scaled_width <= 0 || ig->scaled_height <= 0)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
|
|
||||||
|
if (parent == NULL)
|
||||||
|
size = (ig->src_h - 1) * stride + ig->src_w * 4;
|
||||||
|
if (image_check_limits(parent, size) != 0)
|
||||||
|
return (NULL);
|
||||||
|
|
||||||
im = image_alloc(ig, stride, pixels);
|
im = image_alloc(ig, stride, pixels);
|
||||||
im->parent = parent;
|
im->parent = parent;
|
||||||
if (parent != NULL) {
|
if (parent == NULL)
|
||||||
|
image_size += im->size;
|
||||||
|
else {
|
||||||
im->order_id = parent->order_id;
|
im->order_id = parent->order_id;
|
||||||
|
TAILQ_INSERT_TAIL(&parent->children, im, child_entry);
|
||||||
image_ref(parent);
|
image_ref(parent);
|
||||||
}
|
}
|
||||||
|
TAILQ_INSERT_HEAD(&all_images, im, all_entry);
|
||||||
|
image_touch(im);
|
||||||
return (im);
|
return (im);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,10 +274,15 @@ image_free(struct image *im)
|
|||||||
fatalx("invalid image reference %u", im->id);
|
fatalx("invalid image reference %u", im->id);
|
||||||
if (--im->references == 0) {
|
if (--im->references == 0) {
|
||||||
RB_REMOVE(images, &images, im);
|
RB_REMOVE(images, &images, im);
|
||||||
if (im->parent == NULL)
|
TAILQ_REMOVE(&all_images, im, all_entry);
|
||||||
|
image_count--;
|
||||||
|
if (im->parent == NULL) {
|
||||||
|
image_size -= im->size;
|
||||||
free(im->pixels);
|
free(im->pixels);
|
||||||
else
|
} else {
|
||||||
|
TAILQ_REMOVE(&im->parent->children, im, child_entry);
|
||||||
image_free(im->parent);
|
image_free(im->parent);
|
||||||
|
}
|
||||||
free(im);
|
free(im);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
regress/image-sixel-limits.sh
Executable file
76
regress/image-sixel-limits.sh
Executable file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Check image quotas, last use order, copied grids and released image slots.
|
||||||
|
|
||||||
|
PATH=/bin:/usr/bin
|
||||||
|
TERM=screen
|
||||||
|
export TERM
|
||||||
|
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||||
|
command -v python3 >/dev/null || exit 101
|
||||||
|
|
||||||
|
python3 -B - "$TEST_TMUX" <<'PY'
|
||||||
|
import sys
|
||||||
|
from image_test import image_test, sixel
|
||||||
|
from image_terminal import Terminal
|
||||||
|
|
||||||
|
|
||||||
|
def image(colour):
|
||||||
|
"""Build one solid image cell which advances the cursor by one row."""
|
||||||
|
return sixel(8, 16, [colour], [0] * (8 * 16))
|
||||||
|
|
||||||
|
|
||||||
|
def snapshot(test, client, rows):
|
||||||
|
"""Check the image cells in the first column after a full redraw."""
|
||||||
|
test.redraw()
|
||||||
|
terminal = Terminal()
|
||||||
|
terminal.feed(client.data)
|
||||||
|
expected = {(x, y * 16 + py): colour for y, colour in rows.items()
|
||||||
|
for py in range(16) for x in range(8)}
|
||||||
|
terminal.check(expected)
|
||||||
|
|
||||||
|
|
||||||
|
red, green, blue = (255, 0, 0), (0, 255, 0), (0, 0, 255)
|
||||||
|
red_image = image((100, 0, 0))
|
||||||
|
green_image = image((0, 100, 0))
|
||||||
|
blue_image = image((0, 0, 100))
|
||||||
|
|
||||||
|
with image_test(sys.argv[1]) as test:
|
||||||
|
client = test.attach()
|
||||||
|
test.emit(red_image + green_image * 999)
|
||||||
|
test.emit(blue_image)
|
||||||
|
snapshot(test, client, dict.fromkeys(range(14), green) | {14: blue})
|
||||||
|
# Images in scrollback count towards the quota; the oldest one is gone.
|
||||||
|
test.run('copy-mode')
|
||||||
|
test.run('send-keys', '-X', 'history-top')
|
||||||
|
snapshot(test, client, dict.fromkeys(range(1, 16), green))
|
||||||
|
|
||||||
|
with image_test(sys.argv[1]) as test:
|
||||||
|
client = test.attach()
|
||||||
|
test.emit(red_image + green_image * 999)
|
||||||
|
test.run('copy-mode')
|
||||||
|
test.run('send-keys', '-X', 'history-top')
|
||||||
|
snapshot(test, client, {0: red} | dict.fromkeys(range(1, 16), green))
|
||||||
|
# The copied grid keeps these old images in use while a new window fills.
|
||||||
|
test.run('new-window', '-n', 'quota', '/bin/sh', '-c',
|
||||||
|
"stty raw -echo; printf '\033]2;QUOTA\007'; exec cat")
|
||||||
|
test.wait(lambda: test.run('display-message', '-p',
|
||||||
|
'#{pane_title}') == 'QUOTA')
|
||||||
|
test.emit(blue_image)
|
||||||
|
test.run('select-window', '-t', 'image:0')
|
||||||
|
snapshot(test, client, {0: red} | dict.fromkeys(range(1, 16), green))
|
||||||
|
# The first image below the redrawn rows was evicted from both grids.
|
||||||
|
test.run('send-keys', '-X', 'scroll-down')
|
||||||
|
snapshot(test, client, dict.fromkeys(range(15), green))
|
||||||
|
test.run('send-keys', '-X', 'cancel')
|
||||||
|
test.run('copy-mode')
|
||||||
|
test.run('send-keys', '-X', 'history-top')
|
||||||
|
test.run('send-keys', '-X', 'scroll-down')
|
||||||
|
snapshot(test, client, dict.fromkeys(range(15), green))
|
||||||
|
|
||||||
|
with image_test(sys.argv[1]) as test:
|
||||||
|
client = test.attach()
|
||||||
|
# Replacing an image releases its slot rather than exhausting the quota.
|
||||||
|
test.emit((b'\033[H' + green_image) * 1001)
|
||||||
|
test.emit(b'\033[3;1H' + blue_image)
|
||||||
|
snapshot(test, client, {0: green, 2: blue})
|
||||||
|
PY
|
||||||
@@ -102,6 +102,7 @@ struct image *image_find(u_int);
|
|||||||
u_int image_get_id(struct image *);
|
u_int image_get_id(struct image *);
|
||||||
void image_ref(struct image *);
|
void image_ref(struct image *);
|
||||||
void image_free(struct image *);
|
void image_free(struct image *);
|
||||||
|
void image_touch(struct image *);
|
||||||
struct image_geom *image_get_geometry(struct image *);
|
struct image_geom *image_get_geometry(struct image *);
|
||||||
u_char *image_get_pixels(struct image *, size_t *, size_t *);
|
u_char *image_get_pixels(struct image *, size_t *, size_t *);
|
||||||
int image_get_bounds(struct image_geom *, u_int, u_int, u_int,
|
int image_get_bounds(struct image_geom *, u_int, u_int, u_int,
|
||||||
@@ -118,6 +119,7 @@ void image_grid_add(struct grid *, struct image *, u_int, u_int,
|
|||||||
void image_grid_remove(struct grid *, u_int, u_int, u_int, u_int);
|
void image_grid_remove(struct grid *, u_int, u_int, u_int, u_int);
|
||||||
void image_grid_free_line(struct grid *, struct grid_line *);
|
void image_grid_free_line(struct grid *, struct grid_line *);
|
||||||
void image_grid_free(struct grid *);
|
void image_grid_free(struct grid *);
|
||||||
|
void image_grid_remove_all(struct image *);
|
||||||
void image_grid_move_cells(struct grid *, u_int, u_int, u_int,
|
void image_grid_move_cells(struct grid *, u_int, u_int, u_int,
|
||||||
u_int);
|
u_int);
|
||||||
void image_grid_duplicate_lines(struct grid *, u_int, struct grid *,
|
void image_grid_duplicate_lines(struct grid *, u_int, struct grid *,
|
||||||
|
|||||||
Reference in New Issue
Block a user