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