Add image limit options.

This commit is contained in:
Nicholas Marriott committed 2026-10-17 17:10:08 +01:00
1 parent 14443fc90a
commit 66a8ae0058
11 files changed
+224 -58

No files matched your search

+18 -11
View File
@@ -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
View File
@@ -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;
+19 -12
View File
@@ -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);
+31
View File
@@ -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,
+9 -7
View File
@@ -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
View File
@@ -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')
+97
View File
@@ -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
+7 -3
View File
@@ -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
+4 -3
View File
@@ -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\\')
-3
View File
@@ -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
+6
View File
@@ -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.