mirror of
https://github.com/tmux/tmux.git
synced 2026-10-28 10:04:33 +08:00
Add image limit options.
This commit is contained in:
1 parent
14443fc90a
commit
66a8ae0058
11 files changed
+224
-58
No files matched your search
+18
-11
@@ -434,19 +434,20 @@ static int
|
||||
kitty_append_data(struct kitty_state *ks, const u_char *data, size_t size)
|
||||
{
|
||||
u_char *d;
|
||||
size_t dsize, allocated;
|
||||
size_t dsize, allocated, limit;
|
||||
|
||||
if (size > IMAGE_SIZE_LIMIT)
|
||||
limit = options_get_number(global_options, "image-size-limit");
|
||||
if (size > limit || ks->size > limit)
|
||||
return (-1);
|
||||
|
||||
d = image_base64_decode(data, size, IMAGE_SIZE_LIMIT, &dsize);
|
||||
d = image_base64_decode(data, size, limit, &dsize);
|
||||
if (d == NULL)
|
||||
return (-1);
|
||||
if (dsize == 0) {
|
||||
free(d);
|
||||
return (0);
|
||||
}
|
||||
if (dsize > IMAGE_SIZE_LIMIT - ks->size) {
|
||||
if (dsize > limit - ks->size) {
|
||||
free(d);
|
||||
return (-1);
|
||||
}
|
||||
@@ -455,8 +456,12 @@ kitty_append_data(struct kitty_state *ks, const u_char *data, size_t size)
|
||||
allocated = ks->allocated;
|
||||
if (allocated == 0)
|
||||
allocated = 4096;
|
||||
while (allocated < ks->size + dsize)
|
||||
allocated *= 2;
|
||||
while (allocated < ks->size + dsize) {
|
||||
if (allocated > limit / 2)
|
||||
allocated = limit;
|
||||
else
|
||||
allocated *= 2;
|
||||
}
|
||||
ks->data = xrealloc(ks->data, allocated);
|
||||
ks->allocated = allocated;
|
||||
}
|
||||
@@ -505,7 +510,7 @@ kitty_place_image(struct image *src, struct kitty_state *ks, u_int xpixel,
|
||||
enum image_type type = image_get_type(src);
|
||||
struct image_geom ig = { 0 };
|
||||
u_char *pixels;
|
||||
size_t stride;
|
||||
size_t stride, limit;
|
||||
uint64_t cw, ch;
|
||||
double size;
|
||||
|
||||
@@ -565,9 +570,10 @@ kitty_place_image(struct image *src, struct kitty_state *ks, u_int xpixel,
|
||||
ig.scaled_width = (double)ig.sx * xpixel - ig.canvas_xoff;
|
||||
ig.scaled_height = (double)ig.sy * ypixel - ig.canvas_yoff;
|
||||
}
|
||||
limit = options_get_number(global_options, "image-size-limit");
|
||||
if (ig.sx > USHRT_MAX ||
|
||||
ig.sy > USHRT_MAX ||
|
||||
ig.sx * ig.sy > IMAGE_SIZE_LIMIT)
|
||||
ig.sx * ig.sy > limit)
|
||||
return (NULL);
|
||||
|
||||
/* Record geometry in the input terminal's pixel coordinates. */
|
||||
@@ -595,7 +601,7 @@ kitty_parse_input(struct input_ctx *ictx, struct window_pane *wp,
|
||||
const u_char *found;
|
||||
const char *m;
|
||||
u_char *p = NULL;
|
||||
size_t csize, psize;
|
||||
size_t csize, psize, limit;
|
||||
u_int sx, sy;
|
||||
uint64_t cw, ch;
|
||||
struct image *src = NULL, *im = NULL;
|
||||
@@ -714,14 +720,15 @@ kitty_parse_input(struct input_ctx *ictx, struct window_pane *wp,
|
||||
|
||||
if (ks->compression != 'o' && ks->compression != 'z')
|
||||
goto out;
|
||||
limit = options_get_number(global_options, "image-size-limit");
|
||||
switch (ks->format) {
|
||||
case 24:
|
||||
case 32:
|
||||
p = image_raw_decode(ks->data, ks->size, IMAGE_SIZE_LIMIT,
|
||||
p = image_raw_decode(ks->data, ks->size, limit,
|
||||
ks->format, ks->width, ks->height, ks->compression == 'z');
|
||||
break;
|
||||
case 100:
|
||||
p = image_png_decode(ks->data, ks->size, IMAGE_SIZE_LIMIT,
|
||||
p = image_png_decode(ks->data, ks->size, limit,
|
||||
ks->compression == 'z', ks->data_size, &ks->width,
|
||||
&ks->height);
|
||||
break;
|
||||
|
||||
+12
-7
@@ -42,7 +42,7 @@
|
||||
(SIXEL_HGRAM_LEVELS * SIXEL_HGRAM_LEVELS * SIXEL_HGRAM_LEVELS)
|
||||
|
||||
/* Maximum number of bytes in the SIXEL cache. */
|
||||
#define SIXEL_CACHE_SIZE_LIMIT IMAGE_SIZE_LIMIT
|
||||
#define SIXEL_CACHE_SIZE_LIMIT (64 * 1024 * 1024)
|
||||
|
||||
/* Line of a SIXEL image. */
|
||||
struct sixel_line {
|
||||
@@ -80,9 +80,10 @@ struct sixel_image {
|
||||
u_int dy;
|
||||
u_int dc;
|
||||
|
||||
/* Lines of palette indexes and the bytes allocated for them. */
|
||||
/* Lines of palette indexes, allocated bytes and size limit. */
|
||||
struct sixel_line *lines;
|
||||
size_t allocated;
|
||||
size_t limit;
|
||||
};
|
||||
|
||||
/* One colour's encoded columns in the current six-row band. */
|
||||
@@ -211,7 +212,7 @@ sixel_parse_expand_lines(struct sixel_image *si, u_int y)
|
||||
height = y;
|
||||
if (height > SIXEL_HEIGHT_LIMIT)
|
||||
return (1);
|
||||
if (si->x * height > IMAGE_SIZE_LIMIT / 4)
|
||||
if (si->x * height > si->limit / 4)
|
||||
return (1);
|
||||
if (y <= si->y)
|
||||
return (0);
|
||||
@@ -231,12 +232,12 @@ sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x)
|
||||
width = x;
|
||||
if (width > SIXEL_WIDTH_LIMIT)
|
||||
return (1);
|
||||
if (width * si->y > IMAGE_SIZE_LIMIT / 4)
|
||||
if (width * si->y > si->limit / 4)
|
||||
return (1);
|
||||
if (x <= sl->x)
|
||||
return (0);
|
||||
added = (width - sl->x) * sizeof *sl->data;
|
||||
if (added > IMAGE_SIZE_LIMIT / 2 - si->allocated)
|
||||
if (added > si->limit / 2 - si->allocated)
|
||||
return (1);
|
||||
si->x = width;
|
||||
sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data);
|
||||
@@ -497,6 +498,7 @@ sixel_parse(const char *buf, size_t len, u_int p2, u_int xpixel, u_int ypixel)
|
||||
}
|
||||
|
||||
si = xcalloc(1, sizeof *si);
|
||||
si->limit = options_get_number(global_options, "image-size-limit");
|
||||
si->xpixel = xpixel;
|
||||
si->ypixel = ypixel;
|
||||
si->p2 = p2;
|
||||
@@ -632,7 +634,7 @@ sixel_to_image(struct sixel_image *si)
|
||||
u_char *pixels, *pixel, r, g, b;
|
||||
u_int x, y, c, sx, sy;
|
||||
|
||||
if ((uint64_t)si->x * si->y > IMAGE_SIZE_LIMIT / 4)
|
||||
if ((uint64_t)si->x * si->y > si->limit / 4)
|
||||
return (NULL);
|
||||
|
||||
sixel_size_in_cells(si, &sx, &sy);
|
||||
@@ -1173,6 +1175,7 @@ sixel_make_image(struct image *im, u_int cx, u_int cy, u_int csx, u_int csy,
|
||||
u_int r, g, b, c, i, nc;
|
||||
uint64_t dst_w, dst_h, content_width, content_height;
|
||||
uint64_t left, right, top, bottom;
|
||||
size_t limit;
|
||||
|
||||
/* Work out the requested cell crop in destination pixel coordinates. */
|
||||
src.image = im;
|
||||
@@ -1210,7 +1213,8 @@ sixel_make_image(struct image *im, u_int cx, u_int cy, u_int csx, u_int csy,
|
||||
sy > SIXEL_HEIGHT_LIMIT) {
|
||||
return (NULL);
|
||||
}
|
||||
if (sx * sy > IMAGE_SIZE_LIMIT / 4)
|
||||
limit = options_get_number(global_options, "image-size-limit");
|
||||
if (sx * sy > limit / 4)
|
||||
return (NULL);
|
||||
|
||||
/* Map output pixels using their position in the full canvas. */
|
||||
@@ -1228,6 +1232,7 @@ sixel_make_image(struct image *im, u_int cx, u_int cy, u_int csx, u_int csy,
|
||||
|
||||
/* Create the indexed image with a SIXEL RGB palette. */
|
||||
si = xcalloc(1, sizeof *si);
|
||||
si->limit = limit;
|
||||
si->ra_x = sx;
|
||||
si->ra_y = sy;
|
||||
si->ncolours = si->used_colours = nc;
|
||||
|
||||
@@ -26,10 +26,6 @@
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/* Maximum number of images and total bytes. */
|
||||
#define IMAGE_MAX_IMAGES 1000
|
||||
#define IMAGE_MAX_BYTES (1024 * 1024 * 1024)
|
||||
|
||||
/* An image. */
|
||||
TAILQ_HEAD(image_list, image);
|
||||
struct image {
|
||||
@@ -148,10 +144,13 @@ image_remove_all(struct image *im)
|
||||
static struct image *
|
||||
image_find_oldest(struct image *keep, size_t size)
|
||||
{
|
||||
struct image *im, *parent;
|
||||
struct image *im, *parent;
|
||||
size_t limit;
|
||||
|
||||
limit = options_get_number(global_options, "image-total-size-limit");
|
||||
TAILQ_FOREACH_REVERSE(im, &all_images, image_list, all_entry) {
|
||||
if (image_size + size > IMAGE_MAX_BYTES && im->parent != NULL)
|
||||
if (im->parent != NULL &&
|
||||
(image_size > limit || size > limit - image_size))
|
||||
continue;
|
||||
for (parent = keep; parent != NULL; parent = parent->parent) {
|
||||
if (parent == im)
|
||||
@@ -167,9 +166,14 @@ image_find_oldest(struct image *keep, size_t size)
|
||||
static int
|
||||
image_can_add(size_t size)
|
||||
{
|
||||
if (image_count >= IMAGE_MAX_IMAGES)
|
||||
u_int count;
|
||||
size_t limit;
|
||||
|
||||
count = options_get_number(global_options, "image-total-count-limit");
|
||||
if (image_count >= count)
|
||||
return (0);
|
||||
if (image_size + size > IMAGE_MAX_BYTES)
|
||||
limit = options_get_number(global_options, "image-total-size-limit");
|
||||
if (image_size > limit || size > limit - image_size)
|
||||
return (0);
|
||||
return (1);
|
||||
}
|
||||
@@ -180,8 +184,10 @@ image_check_limits(struct image *keep, size_t size)
|
||||
{
|
||||
struct image *oldest;
|
||||
struct client *c;
|
||||
size_t limit;
|
||||
|
||||
if (size > IMAGE_MAX_BYTES)
|
||||
limit = options_get_number(global_options, "image-total-size-limit");
|
||||
if (size > limit)
|
||||
return (-1);
|
||||
if (image_can_add(size))
|
||||
return (0);
|
||||
@@ -245,19 +251,20 @@ image_create_with_parent(enum image_type type, struct image *parent,
|
||||
struct image_geom *ig, size_t stride, u_char *pixels)
|
||||
{
|
||||
struct image *im;
|
||||
size_t size = 0;
|
||||
size_t size = 0, limit;
|
||||
|
||||
limit = options_get_number(global_options, "image-size-limit");
|
||||
if (ig->src_w == 0 || ig->src_h == 0)
|
||||
return (NULL);
|
||||
if (ig->canvas_width == 0 || ig->canvas_height == 0)
|
||||
return (NULL);
|
||||
if (ig->sx == 0 || ig->sy == 0 || pixels == NULL)
|
||||
return (NULL);
|
||||
if ((uint64_t)ig->src_w * ig->src_h > IMAGE_SIZE_LIMIT / 4)
|
||||
if ((uint64_t)ig->src_w * ig->src_h > limit / 4)
|
||||
return (NULL);
|
||||
if (ig->sx > USHRT_MAX || ig->sy > USHRT_MAX)
|
||||
return (NULL);
|
||||
if (ig->sx * ig->sy > IMAGE_SIZE_LIMIT || stride < ig->src_w * 4)
|
||||
if (ig->sx * ig->sy > limit || stride < ig->src_w * 4)
|
||||
return (NULL);
|
||||
if (ig->scaled_width <= 0 || ig->scaled_height <= 0)
|
||||
return (NULL);
|
||||
|
||||
@@ -506,6 +506,37 @@ const struct options_table_entry options_table[] = {
|
||||
"Empty does not write a history file."
|
||||
},
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
{ .name = "image-size-limit",
|
||||
.type = OPTIONS_TABLE_NUMBER,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.minimum = 1,
|
||||
.maximum = UINT_MAX,
|
||||
.default_num = 64 * 1024 * 1024,
|
||||
.unit = "bytes",
|
||||
.text = "Maximum size of an uploaded or decoded image."
|
||||
},
|
||||
|
||||
{ .name = "image-total-count-limit",
|
||||
.type = OPTIONS_TABLE_NUMBER,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.minimum = 1,
|
||||
.maximum = INT_MAX,
|
||||
.default_num = 1000,
|
||||
.text = "Maximum number of stored images."
|
||||
},
|
||||
|
||||
{ .name = "image-total-size-limit",
|
||||
.type = OPTIONS_TABLE_NUMBER,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.minimum = 1,
|
||||
.maximum = UINT_MAX,
|
||||
.default_num = 1024 * 1024 * 1024,
|
||||
.unit = "bytes",
|
||||
.text = "Maximum total image pixel memory."
|
||||
},
|
||||
#endif
|
||||
|
||||
{ .name = "input-buffer-size",
|
||||
.type = OPTIONS_TABLE_NUMBER,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
|
||||
@@ -103,6 +103,8 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
||||
if run('display-message', '-p', '#{image_support}') == '0':
|
||||
raise SystemExit(101)
|
||||
run('set', '-g', 'status', 'off')
|
||||
size_limit = 64 * 1024
|
||||
run('set', '-s', 'image-size-limit', str(size_limit))
|
||||
pixel = '/wAA/w=='
|
||||
transmit = 'a=t,i=7,f=32,s=1,v=1'
|
||||
ok = graphics('i=7', 'OK')
|
||||
@@ -360,14 +362,14 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
||||
graphics('a=q,i=7,f=32,s=1,v=1,z=1', pixel) +
|
||||
graphics('a=p,i=7,C=1'), unsupported + missing)
|
||||
|
||||
# Abort immediately when chunked data exceeds 64 MiB, then recover.
|
||||
block = base64.b64encode(b'\0' * (512 * 1024)).decode()
|
||||
# Abort immediately when chunked data exceeds the limit, then recover.
|
||||
block = base64.b64encode(b'\0' * 4096).decode()
|
||||
commands = [graphics(transmit + ',q=1,m=1', block)]
|
||||
commands.extend([graphics('m=1', block)] * 127)
|
||||
commands.extend([graphics('m=1', block)] * 15)
|
||||
commands.extend([graphics('m=1', 'AAAAAA=='),
|
||||
graphics('a=t,i=8,f=32,s=1,v=1', pixel)])
|
||||
check('accumulated-size-limit', commands,
|
||||
invalid + graphics('i=8', 'OK'), timeout=90)
|
||||
invalid + graphics('i=8', 'OK'))
|
||||
|
||||
# Compressed uploads require a test build with zlib.
|
||||
compressed = base64.b64encode(zlib.compress(b'\xff\0\0\xff')).decode()
|
||||
@@ -385,9 +387,9 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
||||
check('compressed-wrong-size',
|
||||
graphics(control.replace('s=1', 's=2'), compressed), invalid)
|
||||
large = base64.b64encode(zlib.compress(
|
||||
b'\0' * (4096 * 4097 * 3))).decode()
|
||||
b'\0' * (128 * 129 * 3))).decode()
|
||||
check('oversized-rgb',
|
||||
graphics('a=t,i=7,f=24,s=4096,v=4097,o=z', large), invalid)
|
||||
graphics('a=t,i=7,f=24,s=128,v=129,o=z', large), invalid)
|
||||
|
||||
# PNG uploads require a test build with libpng.
|
||||
png = b'\x89PNG\r\n\x1a\n'
|
||||
@@ -423,7 +425,7 @@ with tempfile.TemporaryDirectory(prefix='tmux-kitty-protocol-') as tmp:
|
||||
graphics(control + ',S=' + str(len(png) + 1), payload),
|
||||
invalid)
|
||||
check('compressed-png-oversized',
|
||||
graphics(control + ',S=67108865', payload), invalid)
|
||||
graphics(control + ',S=' + str(size_limit + 1), payload), invalid)
|
||||
check('compressed-png-truncated', graphics(sized, payload[:-4]),
|
||||
invalid)
|
||||
check('decoder-controls-reset', graphics(sized + ',q=2', payload) +
|
||||
|
||||
+21
-12
@@ -39,38 +39,42 @@ def snapshot(test, client, expected):
|
||||
|
||||
|
||||
red, green, blue = (255, 0, 0), (0, 255, 0), (0, 0, 255)
|
||||
limit = 16
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-total-count-limit', str(limit))
|
||||
client = test.attach()
|
||||
test.emit(upload(1, red) +
|
||||
b''.join(upload(i, green) for i in range(2, 1001)))
|
||||
test.emit(upload(1001, blue))
|
||||
b''.join(upload(i, green) for i in range(2, limit + 1)))
|
||||
test.emit(upload(limit + 1, blue))
|
||||
# The oldest upload is evicted and its application ID no longer resolves.
|
||||
test.emit(place(1))
|
||||
snapshot(test, client, {})
|
||||
test.emit(place(1001))
|
||||
test.emit(place(limit + 1))
|
||||
snapshot(test, client, cell(blue))
|
||||
test.emit(b'\033[3;1H' + place(2))
|
||||
snapshot(test, client, cell(blue))
|
||||
test.emit(place(1000))
|
||||
test.emit(place(limit))
|
||||
snapshot(test, client, cell(blue) | cell(green, y=2))
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-total-count-limit', str(limit))
|
||||
client = test.attach()
|
||||
test.emit(upload(1, red) + place(1, 1))
|
||||
test.emit(upload(2, green))
|
||||
test.emit(kitty('a=d,d=i,i=1,p=1'))
|
||||
# Losing the last placement keeps the source's original last use order.
|
||||
test.emit(b''.join(upload(i, blue) for i in range(3, 1002)))
|
||||
test.emit(b''.join(upload(i, blue) for i in range(3, limit + 2)))
|
||||
test.emit(place(2))
|
||||
snapshot(test, client, cell(green))
|
||||
test.emit(b'\033[3;1H' + place(1))
|
||||
snapshot(test, client, cell(green))
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-total-count-limit', str(limit))
|
||||
client = test.attach()
|
||||
test.emit(upload(1, red) + b'\033[7;1H' + place(1, 1) +
|
||||
b'\033[H' + place(1, 2) + b'\033[3;1H' + place(1, 3) +
|
||||
b'\033[5;1H' + b''.join(place(1, p) for p in range(4, 1000)))
|
||||
b'\033[5;1H' + b''.join(place(1, p) for p in range(4, limit)))
|
||||
snapshot(test, client,
|
||||
cell(red) | cell(red, y=2) | cell(red, y=4) | cell(red, y=6))
|
||||
# Redraw order differs from creation order: the top view is oldest.
|
||||
@@ -78,30 +82,33 @@ with image_test(sys.argv[1]) as test:
|
||||
snapshot(test, client, cell(red, y=2) | cell(red, y=4) | cell(red, y=6))
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-total-count-limit', str(limit))
|
||||
client = test.attach()
|
||||
test.emit(upload(1, red) + b''.join(place(1, p) for p in range(1, 1000)))
|
||||
test.emit(upload(1, red) + b''.join(place(1, p) for p in range(1, limit)))
|
||||
test.emit(upload(2, green) + place(2, 1))
|
||||
snapshot(test, client, cell(green))
|
||||
# The shared source survives; only its two oldest views were evicted.
|
||||
test.emit(kitty('a=d,d=I,i=2'))
|
||||
snapshot(test, client, cell(red))
|
||||
test.emit(b''.join(kitty('a=d,d=i,i=1,p=%d' % p)
|
||||
for p in range(3, 1000)))
|
||||
for p in range(3, limit)))
|
||||
snapshot(test, client, {})
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-total-count-limit', str(limit))
|
||||
client = test.attach()
|
||||
test.emit(sixel(8, 16, [(100, 0, 0)], [0] * (8 * 16)))
|
||||
# Kitty views share the same limit as the older SIXEL source.
|
||||
test.emit(b'\033[3;1H' + upload(1, green) +
|
||||
b''.join(place(1, p) for p in range(1, 1000)))
|
||||
b''.join(place(1, p) for p in range(1, limit)))
|
||||
snapshot(test, client, cell(green, y=2))
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-total-count-limit', str(limit))
|
||||
client = test.attach()
|
||||
test.emit(upload(1, red) + place(1))
|
||||
test.emit(b'\033[3;1H' + upload(2, green) +
|
||||
b''.join(place(2, p) for p in range(1, 998)))
|
||||
b''.join(place(2, p) for p in range(1, limit - 2)))
|
||||
snapshot(test, client, cell(red) | cell(green, y=2))
|
||||
# SIXEL input also evicts Kitty placements and their uploaded image IDs.
|
||||
test.emit(b'\033[5;1H' +
|
||||
@@ -114,10 +121,11 @@ with image_test(sys.argv[1]) as test:
|
||||
snapshot(test, client, cell(green, y=2) | cell(blue, y=4) | cell(blue, y=6))
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-total-count-limit', str(limit))
|
||||
client = test.attach()
|
||||
test.emit(upload(1, red) + place(1))
|
||||
test.emit(b'\033[?1049h\033[H' + upload(2, green) +
|
||||
b''.join(place(2, p) for p in range(1, 1000)))
|
||||
b''.join(place(2, p) for p in range(1, limit)))
|
||||
# Eviction reaches the saved primary grid and removes its Kitty ID.
|
||||
test.emit(b'\033[?1049l')
|
||||
snapshot(test, client, {})
|
||||
@@ -125,6 +133,7 @@ with image_test(sys.argv[1]) as test:
|
||||
snapshot(test, client, {})
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-total-count-limit', str(limit))
|
||||
client = test.attach()
|
||||
test.emit(sixel(8, 16, [(100, 0, 0)], [0] * (8 * 16)))
|
||||
test.run('copy-mode')
|
||||
@@ -133,7 +142,7 @@ with image_test(sys.argv[1]) as test:
|
||||
"stty raw -echo; printf '\033]2;QUOTA\007'; exec cat")
|
||||
test.wait(lambda: test.run('display-message', '-p',
|
||||
'#{pane_title}') == 'QUOTA')
|
||||
test.emit(upload(1, green) + b''.join(place(1, p) for p in range(1, 1000)))
|
||||
test.emit(upload(1, green) + b''.join(place(1, p) for p in range(1, limit)))
|
||||
test.run('select-window', '-t', 'image:0')
|
||||
snapshot(test, client, {})
|
||||
test.run('send-keys', '-X', 'cancel')
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Runtime image size, memory and count limits with small uploads.
|
||||
|
||||
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
|
||||
import zlib
|
||||
from image_test import image_test, kitty, png, sixel
|
||||
from image_terminal import Terminal
|
||||
|
||||
|
||||
def snapshot(test, client, expected):
|
||||
"""Check the image pixels after a full redraw."""
|
||||
test.redraw()
|
||||
terminal = Terminal()
|
||||
terminal.feed(client.data)
|
||||
terminal.check(expected)
|
||||
|
||||
|
||||
red, green, blue = (255, 0, 0), (0, 255, 0), (0, 0, 255)
|
||||
raw = bytes((*red, 255)) * 256
|
||||
with image_test(sys.argv[1]) as test:
|
||||
client = test.attach()
|
||||
for option, value in [('image-size-limit', 64 * 1024 * 1024),
|
||||
('image-total-count-limit', 1000),
|
||||
('image-total-size-limit', 1024 * 1024 * 1024)]:
|
||||
assert int(test.run('show-options', '-sv', option)) == value, option
|
||||
test.run('set-option', '-s', 'image-size-limit', '1024')
|
||||
expected = {(x, y): red for y in range(16) for x in range(16)}
|
||||
|
||||
# Chunked uploads can fill the limit exactly and reject the next bytes.
|
||||
command = kitty('a=T,i=1,f=32,s=16,v=16,C=1,m=1', raw[:512]) + \
|
||||
kitty('m=0', raw[512:])
|
||||
test.emit(command)
|
||||
snapshot(test, client, expected)
|
||||
test.emit(b'\033[2J\033[H' + command.replace(b'v=16', b'v=17') +
|
||||
kitty('a=p,i=1,C=1'))
|
||||
snapshot(test, client, expected)
|
||||
test.emit(b'\033[2J\033[H' +
|
||||
kitty('a=T,i=2,f=32,s=16,v=17,C=1,m=1', raw[:512]) +
|
||||
kitty('m=1', raw[512:]) + kitty('m=0', bytes((*red, 255))) +
|
||||
kitty('a=p,i=2,C=1'))
|
||||
snapshot(test, client, {})
|
||||
|
||||
# Compressed raw and PNG data must also respect the decoded pixel limit.
|
||||
for height in (16, 17):
|
||||
pixels = bytes((*red, 255)) * 16 * height
|
||||
commands = [kitty('a=T,f=32,s=16,v=%d,o=z,C=1' % height,
|
||||
zlib.compress(pixels)), kitty('a=T,f=100,C=1',
|
||||
png(16, height, [bytes((*red, 255)) * 16] * height)),
|
||||
sixel(16, height, [(100, 0, 0)], [0] * (16 * height))]
|
||||
for command in commands:
|
||||
test.emit(b'\033[2J\033[H' + command)
|
||||
snapshot(test, client, expected if height == 16 else {})
|
||||
|
||||
# Lowering the limit during an upload rejects the buffered excess.
|
||||
test.emit(kitty('a=t,i=3,f=32,s=16,v=16,m=1', raw[:768]))
|
||||
test.run('set-option', '-s', 'image-size-limit', '512')
|
||||
test.emit(kitty('m=0', raw[768:]) + kitty('a=p,i=3,C=1'))
|
||||
snapshot(test, client, {})
|
||||
test.emit(kitty('a=T,f=32,s=1,v=1,c=1,r=1,C=1', bytes((*green, 255))))
|
||||
snapshot(test, client, {(x, y): green
|
||||
for y in range(16) for x in range(8)})
|
||||
|
||||
# Transfer buffers grow correctly with a limit that is not a power of two.
|
||||
test.run('set-option', '-s', 'image-size-limit', '6000')
|
||||
pixels = bytes((*red, 255)) * 1500
|
||||
test.emit(b'\033[2J\033[H' +
|
||||
kitty('a=T,f=32,s=25,v=60,C=1,m=1', pixels[:3000]) +
|
||||
kitty('m=0', pixels[3000:]))
|
||||
snapshot(test, client, {(x, y): red
|
||||
for y in range(60) for x in range(25)})
|
||||
|
||||
# Sources share their pixels with placements but count as separate images.
|
||||
for option, limit in [('image-total-count-limit', 4),
|
||||
('image-total-size-limit', 8)]:
|
||||
with image_test(sys.argv[1]) as test:
|
||||
client = test.attach()
|
||||
test.run('set-option', '-s', option, str(limit))
|
||||
for image_id, colour in [(1, red), (2, green)]:
|
||||
test.emit(('\033[%d;1H' % image_id).encode() +
|
||||
kitty('a=T,i=%d,f=32,s=1,v=1,c=1,r=1,C=1' % image_id,
|
||||
bytes((*colour, 255))))
|
||||
snapshot(test, client, {(x, y): red if y < 16 else green
|
||||
for y in range(32) for x in range(8)})
|
||||
test.run('set-option', '-s', option, str(limit // 2))
|
||||
test.emit(b'\033[3;1H' +
|
||||
kitty('a=T,i=3,f=32,s=1,v=1,c=1,r=1,C=1', bytes((*blue, 255))))
|
||||
snapshot(test, client, {(x, y): blue
|
||||
for y in range(32, 48) for x in range(8)})
|
||||
PY
|
||||
@@ -33,10 +33,12 @@ 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))
|
||||
limit = 32
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-total-count-limit', str(limit))
|
||||
client = test.attach()
|
||||
test.emit(red_image + green_image * 999)
|
||||
test.emit(red_image + green_image * (limit - 1))
|
||||
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.
|
||||
@@ -45,8 +47,9 @@ with image_test(sys.argv[1]) as test:
|
||||
snapshot(test, client, dict.fromkeys(range(1, 16), green))
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-total-count-limit', str(limit))
|
||||
client = test.attach()
|
||||
test.emit(red_image + green_image * 999)
|
||||
test.emit(red_image + green_image * (limit - 1))
|
||||
test.run('copy-mode')
|
||||
test.run('send-keys', '-X', 'history-top')
|
||||
snapshot(test, client, {0: red} | dict.fromkeys(range(1, 16), green))
|
||||
@@ -68,9 +71,10 @@ with image_test(sys.argv[1]) as test:
|
||||
snapshot(test, client, dict.fromkeys(range(15), green))
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-total-count-limit', str(limit))
|
||||
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[H' + green_image) * (limit + 1))
|
||||
test.emit(b'\033[3;1H' + blue_image)
|
||||
snapshot(test, client, {0: green, 2: blue})
|
||||
PY
|
||||
@@ -13,6 +13,7 @@ import sys
|
||||
from image_test import image_test, sixel, check_image
|
||||
|
||||
with image_test(sys.argv[1]) as test:
|
||||
test.run('set-option', '-s', 'image-size-limit', '4096')
|
||||
client = test.attach()
|
||||
red = (255, 0, 0)
|
||||
valid = sixel(8, 16, [(100, 0, 0)], [0] * (8 * 16))
|
||||
@@ -54,10 +55,10 @@ with image_test(sys.argv[1]) as test:
|
||||
b'q', b'q#0;2;100', b'q!0~', b'q!10001~', b'q#1025~',
|
||||
b'q#0;2;101;0;0~', b'q#0;1;361;50;100~',
|
||||
b'q"1;1;10001;1', b'q"1;1;1;10001',
|
||||
b'q"1;1;4096;4097',
|
||||
b'q"1;1;32;33',
|
||||
b'q"1;1;18446744073709551616;1',
|
||||
b'q"1;1;10000;1#0;2;100;0;0@' + b'-' * 300 + b'@',
|
||||
b'q"1;1;1;10000#0;2;100;0;0!2000@',
|
||||
b'q"1;1;32;1#0;2;100;0;0@' + b'-' * 6 + b'@',
|
||||
b'q"1;1;1;32#0;2;100;0;0!33@',
|
||||
]
|
||||
for body in rejected:
|
||||
test.emit(b'\033[2J\033[H\033P9;1' + body + b'\033\\')
|
||||
|
||||
@@ -37,9 +37,6 @@ struct sixel_image;
|
||||
struct tty;
|
||||
struct window_pane;
|
||||
|
||||
/* Maximum image size in bytes. */
|
||||
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
|
||||
|
||||
/* Backend draws images as graphics. */
|
||||
#define IMAGE_BACKEND_GRAPHICAL 0x1
|
||||
|
||||
|
||||
@@ -5112,6 +5112,12 @@ option.
|
||||
If not empty, a file to which
|
||||
.Nm
|
||||
will write command prompt history on exit and load it from on start.
|
||||
.It Ic image\-size\-limit Ar bytes
|
||||
Maximum size of a single image in bytes.
|
||||
.It Ic image\-total\-count\-limit Ar number
|
||||
Maximum number of stored images.
|
||||
.It Ic image\-total\-size\-limit Ar bytes
|
||||
Maximum total bytes for all stored images.
|
||||
.It Ic input\-buffer\-size Ar bytes
|
||||
Maximum of bytes allowed to read in escape and control sequences.
|
||||
Once reached, the sequence will be discarded.
|
||||
|
||||
Reference in new issue
Block a user