Merge master into 4902-image-support

This commit is contained in:
Nicholas Marriott
2026-10-13 21:18:26 +01:00
25 changed files with 2471 additions and 2117 deletions

View File

@@ -211,6 +211,7 @@ dist_tmux_SOURCES = \
style.c \ style.c \
tmux-protocol.h \ tmux-protocol.h \
tmux.c \ tmux.c \
tmux-image.h \
tmux.h \ tmux.h \
tty-acs.c \ tty-acs.c \
tty-draw.c \ tty-draw.c \
@@ -252,7 +253,12 @@ endif
# Enable image support. # Enable image support.
if ENABLE_IMAGES if ENABLE_IMAGES
dist_tmux_SOURCES += image.c image-sixel.c image-kitty.c image-fallback.c dist_tmux_SOURCES += \
image.c \
image-grid.c \
image-sixel.c \
image-kitty.c \
image-fallback.c
endif endif
# Add bits for fuzzing if enabled. # Add bits for fuzzing if enabled.

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: format.c,v 1.427 2026/10/12 12:22:42 nicm Exp $ */ /* $OpenBSD: format.c,v 1.428 2026/10/13 10:21:58 nicm Exp $ */
/* /*
* Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2011 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -2354,6 +2354,17 @@ format_cb_pane_last_prompt_time(struct format_tree *ft)
return (NULL); return (NULL);
} }
/* Callback for pane_command_line. */
static void *
format_cb_pane_command_line(struct format_tree *ft)
{
struct window_pane *wp = ft->wp;
if (wp != NULL && wp->cmd_line != NULL)
return (xstrdup(wp->cmd_line));
return (NULL);
}
/* Callback for pane_command_start_time. */ /* Callback for pane_command_start_time. */
static void * static void *
format_cb_pane_command_start_time(struct format_tree *ft) format_cb_pane_command_start_time(struct format_tree *ft)
@@ -3843,6 +3854,9 @@ static const struct format_table_entry format_table[] = {
{ "pane_command_end_time", FORMAT_TABLE_TIME, { "pane_command_end_time", FORMAT_TABLE_TIME,
format_cb_pane_command_end_time format_cb_pane_command_end_time
}, },
{ "pane_command_line", FORMAT_TABLE_STRING,
format_cb_pane_command_line
},
{ "pane_command_running", FORMAT_TABLE_STRING, { "pane_command_running", FORMAT_TABLE_STRING,
format_cb_pane_command_running format_cb_pane_command_running
}, },

View File

@@ -74,6 +74,10 @@ grid_view_clear_history(struct grid *gd, u_int bg)
gl = grid_get_line(gd, grid_view_y(gd, yy)); gl = grid_get_line(gd, grid_view_y(gd, yy));
if (gl->cellused != 0) if (gl->cellused != 0)
last = yy + 1; last = yy + 1;
#ifdef ENABLE_IMAGES
else if (image_grid_line_has_images(gl))
last = yy + 1;
#endif
} }
if (last == 0) { if (last == 0) {
grid_view_clear(gd, 0, 0, gd->sx, gd->sy, bg); grid_view_clear(gd, 0, 0, gd->sx, gd->sy, bg);

13
grid.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: grid.c,v 1.160 2026/10/12 08:08:37 nicm Exp $ */ /* $OpenBSD: grid.c,v 1.161 2026/10/12 15:40:17 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -701,6 +701,9 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
return; return;
if (grid_check_y(gd, __func__, py + ny - 1) != 0) if (grid_check_y(gd, __func__, py + ny - 1) != 0)
return; return;
#ifdef ENABLE_IMAGES
image_grid_remove(gd, px, py, nx, ny);
#endif
for (yy = py; yy < py + ny; yy++) { for (yy = py; yy < py + ny; yy++) {
gl = &gd->linedata[yy]; gl = &gd->linedata[yy];
@@ -1262,17 +1265,13 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
struct grid_line *dstl, *srcl; struct grid_line *dstl, *srcl;
u_int yy; u_int yy;
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
u_int original_dy, original_sy; u_int ody = dy, osy = sy;
#endif #endif
if (dy + ny > dst->hsize + dst->sy) if (dy + ny > dst->hsize + dst->sy)
ny = dst->hsize + dst->sy - dy; ny = dst->hsize + dst->sy - dy;
if (sy + ny > src->hsize + src->sy) if (sy + ny > src->hsize + src->sy)
ny = src->hsize + src->sy - sy; ny = src->hsize + src->sy - sy;
#ifdef ENABLE_IMAGES
original_dy = dy;
original_sy = sy;
#endif
grid_free_lines(dst, dy, ny); grid_free_lines(dst, dy, ny);
for (yy = 0; yy < ny; yy++) { for (yy = 0; yy < ny; yy++) {
@@ -1303,7 +1302,7 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
dy++; dy++;
} }
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
image_grid_duplicate_lines(dst, original_dy, src, original_sy, ny); image_grid_duplicate_lines(dst, ody, src, osy, ny);
#endif #endif
} }

View File

@@ -252,27 +252,31 @@ fallback_block_key(enum image_glyph_detail detail, u_int mask)
static u_char * static u_char *
fallback_make_shades(struct image *im, u_int levels) fallback_make_shades(struct image *im, u_int levels)
{ {
struct image_glyph_data *data = im->fallback_data; struct image_glyph_data *data = image_get_fallback_data(im);
const struct image_cell *cell; const struct image_cell *cell;
int *values, value, represented, error; int *values, value, represented, error;
size_t cells, index; size_t cells, index;
u_int x, y, scan, level, minimum = 255, maximum = 0; u_int x, y, scan, level, minimum = 255, maximum = 0;
u_int sx, sy;
int reverse; int reverse;
u_char *result; u_char *result;
if (data == NULL) if (data == NULL) {
data = im->fallback_data = xcalloc(1, sizeof *data); data = xcalloc(1, sizeof *data);
image_set_fallback_data(im, data);
}
image_get_size_in_cells(im, &sx, &sy);
result = (levels == 5 ? data->shade5 : data->shade8); result = (levels == 5 ? data->shade5 : data->shade8);
if (result != NULL) if (result != NULL)
return (result); return (result);
/* Collect cell brightness and the range used by this image. */ /* Collect cell brightness and the range used by this image. */
cells = (size_t)im->sx * im->sy; cells = (size_t)sx * sy;
values = xcalloc(cells, sizeof *values); values = xcalloc(cells, sizeof *values);
result = xcalloc(cells, 1); result = xcalloc(cells, 1);
for (y = 0; y < im->sy; y++) { for (y = 0; y < sy; y++) {
for (x = 0; x < im->sx; x++) { for (x = 0; x < sx; x++) {
index = (size_t)y * im->sx + x; index = (size_t)y * sx + x;
cell = image_get_cell(im, x, y); cell = image_get_cell(im, x, y);
value = cell->whole.brightness; value = cell->whole.brightness;
if ((u_int)value < minimum) if ((u_int)value < minimum)
@@ -291,11 +295,11 @@ fallback_make_shades(struct image *im, u_int levels)
} }
/* Dither each row, alternating direction to distribute the error. */ /* Dither each row, alternating direction to distribute the error. */
for (y = 0; y < im->sy; y++) { for (y = 0; y < sy; y++) {
reverse = (y & 1); reverse = (y & 1);
for (scan = 0; scan < im->sx; scan++) { for (scan = 0; scan < sx; scan++) {
x = (reverse ? im->sx - scan - 1 : scan); x = (reverse ? sx - scan - 1 : scan);
index = (size_t)y * im->sx + x; index = (size_t)y * sx + x;
value = values[index]; value = values[index];
if (value < 0) if (value < 0)
value = 0; value = 0;
@@ -305,21 +309,21 @@ fallback_make_shades(struct image *im, u_int levels)
result[index] = level; result[index] = level;
represented = level * 255 / (levels - 1); represented = level * 255 / (levels - 1);
error = value - represented; error = value - represented;
if (!reverse && x + 1 < im->sx) if (!reverse && x + 1 < sx)
values[index + 1] += error * 7 / 16; values[index + 1] += error * 7 / 16;
if (reverse && x != 0) if (reverse && x != 0)
values[index - 1] += error * 7 / 16; values[index - 1] += error * 7 / 16;
if (y + 1 == im->sy) if (y + 1 == sy)
continue; continue;
if (!reverse && x != 0) if (!reverse && x != 0)
values[index + im->sx - 1] += error * 3 / 16; values[index + sx - 1] += error * 3 / 16;
if (reverse && x + 1 < im->sx) if (reverse && x + 1 < sx)
values[index + im->sx + 1] += error * 3 / 16; values[index + sx + 1] += error * 3 / 16;
values[index + im->sx] += error * 5 / 16; values[index + sx] += error * 5 / 16;
if (!reverse && x + 1 < im->sx) if (!reverse && x + 1 < sx)
values[index + im->sx + 1] += error / 16; values[index + sx + 1] += error / 16;
if (reverse && x != 0) if (reverse && x != 0)
values[index + im->sx - 1] += error / 16; values[index + sx - 1] += error / 16;
} }
} }
free(values); free(values);
@@ -447,7 +451,7 @@ fallback_get_cell(struct tty *tty, struct image *im, u_int x, u_int y,
enum image_glyph_palette palette; enum image_glyph_palette palette;
enum image_glyph_detail detail; enum image_glyph_detail detail;
u_char *levels, key; u_char *levels, key;
u_int level = 0, nlevels; u_int level = 0, nlevels, sx, sy;
memcpy(out, gc, sizeof *out); memcpy(out, gc, sizeof *out);
cell = image_get_cell(im, x, y); cell = image_get_cell(im, x, y);
@@ -469,7 +473,8 @@ fallback_get_cell(struct tty *tty, struct image *im, u_int x, u_int y,
if (detail == IMAGE_GLYPH_SHADE5) if (detail == IMAGE_GLYPH_SHADE5)
nlevels = 5; nlevels = 5;
levels = fallback_make_shades(im, nlevels); levels = fallback_make_shades(im, nlevels);
level = levels[(size_t)y * im->sx + x]; image_get_size_in_cells(im, &sx, &sy);
level = levels[(size_t)y * sx + x];
key = (detail == IMAGE_GLYPH_SHADE5 ? shades[level] : key = (detail == IMAGE_GLYPH_SHADE5 ? shades[level] :
bold_shades[level]); bold_shades[level]);
if (key == 0) if (key == 0)
@@ -492,7 +497,7 @@ fallback_get_cell(struct tty *tty, struct image *im, u_int x, u_int y,
void void
fallback_free(struct image *im) fallback_free(struct image *im)
{ {
struct image_glyph_data *data = im->fallback_data; struct image_glyph_data *data = image_get_fallback_data(im);
if (data == NULL) if (data == NULL)
return; return;

1491
image-grid.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -595,7 +595,7 @@ kitty_upload(struct tty *tty, struct image *im)
} while (id == 0); } while (id == 0);
entry->server_id = image_get_id(im); entry->server_id = image_get_id(im);
entry->kitty_id = id; entry->kitty_id = id;
entry->order_id = im->order_id; entry->order_id = image_get_order_id(im);
entry->xpixel = tty->xpixel; entry->xpixel = tty->xpixel;
entry->ypixel = tty->ypixel; entry->ypixel = tty->ypixel;
entry->next_placement = 0; entry->next_placement = 0;
@@ -843,7 +843,7 @@ kitty_placements_free(struct kitty_source *source)
for (placement = source->placements; placement != NULL; for (placement = source->placements; placement != NULL;
placement = next) { placement = next) {
next = placement->next; next = placement->next;
image_free(placement->server_id); image_free(image_find(placement->server_id));
free(placement); free(placement);
} }
source->placements = NULL; source->placements = NULL;
@@ -854,7 +854,7 @@ static void
kitty_source_free(struct kitty_source *source) kitty_source_free(struct kitty_source *source)
{ {
kitty_placements_free(source); kitty_placements_free(source);
image_free(source->server_id); image_free(image_find(source->server_id));
free(source); free(source);
} }
@@ -908,11 +908,11 @@ kitty_source_set(struct kitty_context *kc, u_int id, struct image *im)
old_id = source->server_id; old_id = source->server_id;
old = image_find(old_id); old = image_find(old_id);
if (old != NULL) if (old != NULL)
im->order_id = old->order_id; image_set_order_id(im, image_get_order_id(old));
kitty_placements_free(source); kitty_placements_free(source);
image_free(source->server_id); image_free(image_find(source->server_id));
} }
image_ref(image_get_id(im)); image_ref(im);
source->server_id = image_get_id(im); source->server_id = image_get_id(im);
return (old_id); return (old_id);
} }
@@ -945,10 +945,10 @@ kitty_placement_set(struct kitty_context *kc, const struct kitty_state *ks,
old_id = placement->server_id; old_id = placement->server_id;
placement->z = ks->z; placement->z = ks->z;
placement->virtual = ks->virtual; placement->virtual = ks->virtual;
image_ref(image_get_id(im)); image_ref(im);
placement->server_id = image_get_id(im); placement->server_id = image_get_id(im);
if (old_id != 0) if (old_id != 0)
image_free(old_id); image_free(image_find(old_id));
return (old_id); return (old_id);
} }
@@ -964,7 +964,7 @@ kitty_source_get(struct kitty_context *kc, u_int id)
return (NULL); return (NULL);
im = image_find(source->server_id); im = image_find(source->server_id);
if (im != NULL) if (im != NULL)
image_ref(image_get_id(im)); image_ref(im);
return (im); return (im);
} }
@@ -981,7 +981,7 @@ kitty_prune_placements(struct kitty_source *source, struct grid *gd)
if (image_grid_has_image(gd, placement->server_id)) if (image_grid_has_image(gd, placement->server_id))
goto keep; goto keep;
*pp = placement->next; *pp = placement->next;
image_free(placement->server_id); image_free(image_find(placement->server_id));
free(placement); free(placement);
removed++; removed++;
continue; continue;
@@ -1040,7 +1040,7 @@ kitty_delete_images(void *state, struct screen_write_ctx *ctx,
goto keep_placement; goto keep_placement;
} }
*pp = placement->next; *pp = placement->next;
image_free(placement->server_id); image_free(image_find(placement->server_id));
free(placement); free(placement);
removed++; removed++;
continue; continue;
@@ -1272,20 +1272,20 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
view.scaled_height = floor(view.height * height_scale * units); view.scaled_height = floor(view.height * height_scale * units);
if (view.scaled_height < view.height) if (view.scaled_height < view.height)
view.scaled_height = view.height; view.scaled_height = view.height;
view.x_offset = floor(x_offset * units); view.canvas_xoff = floor(x_offset * units);
view.y_offset = floor(y_offset * units); view.canvas_yoff = floor(y_offset * units);
if (view.x_offset > view.canvas_width || if (view.canvas_xoff > view.canvas_width ||
view.scaled_width > view.canvas_width - view.x_offset || view.scaled_width > view.canvas_width - view.canvas_xoff ||
view.y_offset > view.canvas_height || view.canvas_yoff > view.canvas_height ||
view.scaled_height > view.canvas_height - view.y_offset) { view.scaled_height > view.canvas_height - view.canvas_yoff) {
return (NULL); return (NULL);
} }
/* Centre Unicode-placeholder images within their rectangle. */ /* Centre Unicode-placeholder images within their rectangle. */
if (ks->virtual && ks->columns != 0 && ks->rows != 0) { if (ks->virtual && ks->columns != 0 && ks->rows != 0) {
view.x_offset += (view.canvas_width - view.x_offset - view.canvas_xoff += (view.canvas_width - view.canvas_xoff -
view.scaled_width) / 2; view.scaled_width) / 2;
view.y_offset += (view.canvas_height - view.y_offset - view.canvas_yoff += (view.canvas_height - view.canvas_yoff -
view.scaled_height) / 2; view.scaled_height) / 2;
} }
/* Let the output terminal scale unpadded rectangular placements. */ /* Let the output terminal scale unpadded rectangular placements. */
@@ -1297,8 +1297,8 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
view.scaled_width = view.width; view.scaled_width = view.width;
view.canvas_height = view.height; view.canvas_height = view.height;
view.scaled_height = view.height; view.scaled_height = view.height;
view.x_offset = 0; view.canvas_xoff = 0;
view.y_offset = 0; view.canvas_yoff = 0;
} }
im = image_create_view(source, &view); im = image_create_view(source, &view);
if (im != NULL && ks->no_cursor) if (im != NULL && ks->no_cursor)
@@ -1401,17 +1401,17 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
im = NULL; im = NULL;
} else if (ks->virtual) { } else if (ks->virtual) {
im = kitty_place_image(source, ks, xpixel, ypixel); im = kitty_place_image(source, ks, xpixel, ypixel);
image_free(image_get_id(source)); image_free(source);
if (im != NULL) { if (im != NULL) {
(void)kitty_placement_set(kc, ks, im); (void)kitty_placement_set(kc, ks, im);
image_free(image_get_id(im)); image_free(im);
im = NULL; im = NULL;
result->action = 'u'; result->action = 'u';
result->status = KITTY_PARSE_OK; result->status = KITTY_PARSE_OK;
} }
} else { } else {
im = kitty_place_image(source, ks, xpixel, ypixel); im = kitty_place_image(source, ks, xpixel, ypixel);
image_free(image_get_id(source)); image_free(source);
if (im != NULL) { if (im != NULL) {
result->replace_id = result->replace_id =
kitty_placement_set(kc, ks, im); kitty_placement_set(kc, ks, im);
@@ -1524,14 +1524,14 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
result->status = KITTY_PARSE_ERROR; result->status = KITTY_PARSE_ERROR;
else { else {
(void)kitty_placement_set(kc, ks, im); (void)kitty_placement_set(kc, ks, im);
image_free(image_get_id(im)); image_free(im);
im = NULL; im = NULL;
result->action = 'u'; result->action = 'u';
} }
} else { } else {
im = NULL; im = NULL;
} }
image_free(image_get_id(source)); image_free(source);
} }
kitty_state_free(ks); kitty_state_free(ks);
return (im); return (im);

View File

@@ -1695,3 +1695,18 @@ sixel_redraw_start(struct tty *tty, u_int x, u_int y, u_int sx, u_int sy)
tty_repeat_space(tty, sx); tty_repeat_space(tty, sx);
} }
} }
/* Write a SIXEL image and release the parsed data. */
void
sixel_write(struct screen_write_ctx *ctx, struct sixel_image *si, u_int bg)
{
struct image *im;
im = sixel_to_image(si);
if (im == NULL) {
sixel_free(si);
return;
}
image_write_sixel(ctx, im, bg);
image_free(im);
}

1839
image.c

File diff suppressed because it is too large Load Diff

65
input.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: input.c,v 1.273 2026/10/08 07:50:05 nicm Exp $ */ /* $OpenBSD: input.c,v 1.276 2026/10/13 10:21:58 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -2032,6 +2032,7 @@ static void
input_csi_dispatch_sm_private(struct input_ctx *ictx) input_csi_dispatch_sm_private(struct input_ctx *ictx)
{ {
struct screen_write_ctx *sctx = &ictx->ctx; struct screen_write_ctx *sctx = &ictx->ctx;
struct window_pane *wp = ictx->wp;
struct grid_cell *gc = &ictx->cell.cell; struct grid_cell *gc = &ictx->cell.cell;
u_int i; u_int i;
@@ -2093,13 +2094,13 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx)
break; break;
case 2031: case 2031:
screen_write_mode_set(sctx, MODE_THEME_UPDATES); screen_write_mode_set(sctx, MODE_THEME_UPDATES);
if (ictx->wp != NULL) { if (wp != NULL) {
ictx->wp->last_theme = window_pane_get_theme(ictx->wp); wp->last_theme = window_pane_get_theme(wp);
ictx->wp->flags &= ~PANE_THEMECHANGED; wp->flags &= ~PANE_THEMECHANGED;
} }
break; break;
case 2026: case 2026:
screen_write_start_sync(ictx->wp); screen_write_start_sync(wp);
break; break;
default: default:
log_debug("%s: unknown '%c'", __func__, ictx->ch); log_debug("%s: unknown '%c'", __func__, ictx->ch);
@@ -2666,7 +2667,7 @@ input_dcs_dispatch(struct input_ctx *ictx)
p2 = 0; p2 = 0;
si = sixel_parse(buf, len, p1, p2, w->xpixel, w->ypixel); si = sixel_parse(buf, len, p1, p2, w->xpixel, w->ypixel);
if (si != NULL) if (si != NULL)
screen_write_sixelimage(sctx, si, ictx->cell.cell.bg); sixel_write(sctx, si, ictx->cell.cell.bg);
} }
#endif #endif
@@ -2867,7 +2868,7 @@ input_handle_kitty(struct input_ctx *ictx, const u_char *buf, size_t len)
} else if (im != NULL) { } else if (im != NULL) {
image_write_kitty(sctx, im, ictx->cell.cell.bg, image_write_kitty(sctx, im, ictx->cell.cell.bg,
result.image_id, result.placement_id, result.z); result.image_id, result.placement_id, result.z);
image_free(image_get_id(im)); image_free(im);
} }
if (result.action != 'd') if (result.action != 'd')
input_reply_kitty(ictx, &result, "OK"); input_reply_kitty(ictx, &result, "OK");
@@ -3336,6 +3337,52 @@ input_osc_133_exit_status(const char *p)
return (status); return (status);
} }
/* Parse and clean the URL-encoded command line from OSC 133 C. */
static char *
input_osc_133_command_line(const char *p)
{
const char *end;
char line[16385], h[3] = { 0 };
size_t size = 0;
u_char ch;
if (p[1] != ';')
return (NULL);
p += 2;
for (;;) {
end = strchr(p, ';');
if (end == NULL)
end = p + strlen(p);
if (end - p >= 12 && strncmp(p, "cmdline_url=", 12) == 0)
break;
if (*end == '\0')
return (NULL);
p = end + 1;
}
p += 12;
while (p < end) {
if (size == sizeof line - 1)
return (NULL);
ch = *p++;
if (ch == '%') {
if (end - p < 2 ||
!isxdigit((u_char)p[0]) ||
!isxdigit((u_char)p[1]))
return (NULL);
h[0] = p[0];
h[1] = p[1];
ch = strtoul(h, NULL, 16);
if (ch == '\0')
return (NULL);
p += 2;
}
line[size++] = ch;
}
line[size] = '\0';
return (clean_name(line, 1));
}
/* Fire an OSC 133 command event. */ /* Fire an OSC 133 command event. */
static void static void
input_fire_command_event(struct window_pane *wp, const char *name) input_fire_command_event(struct window_pane *wp, const char *name)
@@ -3355,6 +3402,8 @@ input_fire_command_event(struct window_pane *wp, const char *name)
event_payload_set_window(ep, "window", wp->window); event_payload_set_window(ep, "window", wp->window);
event_payload_set_pane(ep, "pane", wp); event_payload_set_pane(ep, "pane", wp);
if (wp->cmd_line != NULL)
event_payload_set_string(ep, "command_line", "%s", wp->cmd_line);
if (wp->cmd_status != -1) if (wp->cmd_status != -1)
event_payload_set_int(ep, "command_status", wp->cmd_status); event_payload_set_int(ep, "command_status", wp->cmd_status);
if (tstart != 0) if (tstart != 0)
@@ -3426,6 +3475,8 @@ input_osc_133(struct input_ctx *ictx, const char *p)
gl->osc133_data.out_start_col = s->cx; gl->osc133_data.out_start_col = s->cx;
} }
if (wp != NULL) { if (wp != NULL) {
free(wp->cmd_line);
wp->cmd_line = input_osc_133_command_line(p);
wp->cmd_start_time = time(NULL); wp->cmd_start_time = time(NULL);
wp->cmd_end_time = 0; wp->cmd_end_time = 0;
wp->flags |= PANE_CMDRUNNING; wp->flags |= PANE_CMDRUNNING;

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: mode-tree.c,v 1.103 2026/09/24 18:59:00 nicm Exp $ */ /* $OpenBSD: mode-tree.c,v 1.104 2026/10/12 18:23:25 nicm Exp $ */
/* /*
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -701,8 +701,17 @@ mode_tree_build(struct mode_tree_data *mtd)
static void static void
mode_tree_remove_ref(struct mode_tree_data *mtd) mode_tree_remove_ref(struct mode_tree_data *mtd)
{ {
if (--mtd->references == 0) if (--mtd->references != 0)
free(mtd); return;
mode_tree_clear_prompt(mtd);
mode_tree_free_items(&mtd->children);
mode_tree_clear_lines(mtd);
screen_free(&mtd->screen);
free(mtd->search);
free(mtd->filter);
free(mtd);
} }
void void
@@ -713,14 +722,6 @@ mode_tree_free(struct mode_tree_data *mtd)
if (mtd->zoomed == 0) if (mtd->zoomed == 0)
server_unzoom_window(wp->window); server_unzoom_window(wp->window);
mode_tree_clear_prompt(mtd);
mode_tree_free_items(&mtd->children);
mode_tree_clear_lines(mtd);
screen_free(&mtd->screen);
free(mtd->search);
free(mtd->filter);
mtd->dead = 1; mtd->dead = 1;
mode_tree_remove_ref(mtd); mode_tree_remove_ref(mtd);
} }
@@ -1095,7 +1096,7 @@ mode_tree_prompt_accept(struct cmdq_item *item, void *data)
struct client *c = cmdq_get_client(item); struct client *c = cmdq_get_client(item);
key_code key = 'y'; key_code key = 'y';
if (mtd->prompt != NULL && c != NULL) if (!mtd->dead && mtd->prompt != NULL && c != NULL)
mode_tree_key(mtd, c, &key, NULL, NULL, NULL); mode_tree_key(mtd, c, &key, NULL, NULL, NULL);
mode_tree_remove_ref(mtd); mode_tree_remove_ref(mtd);
@@ -1122,7 +1123,6 @@ mode_tree_prompt_free_callback(void *data)
mtp->mtd->prompt_data = NULL; mtp->mtd->prompt_data = NULL;
if (mtp->freecb != NULL) if (mtp->freecb != NULL)
mtp->freecb(mtp->data); mtp->freecb(mtp->data);
mode_tree_remove_ref(mtp->mtd);
free(mtp); free(mtp);
} }
@@ -1152,8 +1152,6 @@ mode_tree_set_prompt(struct mode_tree_data *mtd, struct client *c,
mtp->inputcb = inputcb; mtp->inputcb = inputcb;
mtp->freecb = freecb; mtp->freecb = freecb;
mtp->data = data; mtp->data = data;
mtd->references++;
mtd->prompt_top = options_get_number(oo, "status-position") == 0; mtd->prompt_top = options_get_number(oo, "status-position") == 0;
memset(&pd, 0, sizeof pd); memset(&pd, 0, sizeof pd);
@@ -1524,6 +1522,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
if (mtd->prompt != NULL) { if (mtd->prompt != NULL) {
redraw = 0; redraw = 0;
prompt = mtd->prompt; prompt = mtd->prompt;
mtd->references++;
mtp = mtd->prompt_data; mtp = mtd->prompt_data;
if (mtp != NULL) if (mtp != NULL)
@@ -1548,6 +1547,11 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
} }
} else } else
result = prompt_key(prompt, *key, &redraw); result = prompt_key(prompt, *key, &redraw);
if (mtd->dead) {
mode_tree_remove_ref(mtd);
*key = KEYC_NONE;
return (1);
}
if (mtd->prompt_data == mtp && mtp != NULL) if (mtd->prompt_data == mtp && mtp != NULL)
mtp->c = NULL; mtp->c = NULL;
@@ -1563,6 +1567,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
mode_tree_draw(mtd); mode_tree_draw(mtd);
mtd->wp->flags |= PANE_REDRAW; mtd->wp->flags |= PANE_REDRAW;
} }
mode_tree_remove_ref(mtd);
if (result != PROMPT_KEY_NOT_HANDLED) { if (result != PROMPT_KEY_NOT_HANDLED) {
*key = KEYC_NONE; *key = KEYC_NONE;
return (0); return (0);

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: prompt.c,v 1.7 2026/09/09 07:53:03 nicm Exp $ */ /* $OpenBSD: prompt.c,v 1.8 2026/10/13 07:56:00 nicm Exp $ */
/* /*
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1555,14 +1555,25 @@ prompt_complete_add(char ***list, u_int *size, const char *s)
/* Build completion list. */ /* Build completion list. */
static char ** static char **
prompt_complete_commands(u_int *size, const char *s) prompt_complete_commands(u_int *size, const char *s, int at_start)
{ {
char **list = NULL, *tmp; char **list = NULL, *tmp;
const char *value, *cp; const char **layout, *value, *cp;
const struct cmd_entry **cmdent; const struct cmd_entry **cmdent;
size_t slen = strlen(s), valuelen; const struct options_table_entry *oe;
struct options_entry *o; size_t slen = strlen(s), valuelen;
struct options_array_item *a; struct options_entry *o;
struct options_array_item *a;
const char *layouts[] = {
"even-horizontal",
"even-vertical",
"main-horizontal",
"main-horizontal-mirrored",
"main-vertical",
"main-vertical-mirrored",
"tiled",
NULL
};
*size = 0; *size = 0;
for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
@@ -1588,6 +1599,16 @@ prompt_complete_commands(u_int *size, const char *s)
a = options_array_next(a); a = options_array_next(a);
} }
} }
if (at_start)
return (list);
for (oe = options_table; oe->name != NULL; oe++) {
if (strncmp(oe->name, s, slen) == 0)
prompt_complete_add(&list, size, oe->name);
}
for (layout = layouts; *layout != NULL; layout++) {
if (strncmp(*layout, s, slen) == 0)
prompt_complete_add(&list, size, *layout);
}
return (list); return (list);
} }
@@ -1671,11 +1692,10 @@ prompt_complete(struct prompt *pr, const char *word, u_int offset)
char **list = NULL, *out = NULL; char **list = NULL, *out = NULL;
u_int size = 0, i; u_int size = 0, i;
if (pr->type != PROMPT_TYPE_COMMAND || offset != 0 || if (pr->type != PROMPT_TYPE_COMMAND || *word == '\0')
*word == '\0')
return (NULL); return (NULL);
list = prompt_complete_commands(&size, word); list = prompt_complete_commands(&size, word, offset == 0);
if (size == 0) { if (size == 0) {
free(list); free(list);
return (NULL); return (NULL);

View File

@@ -378,4 +378,23 @@ done
[ "$i" -lt 50 ] || fail "tagged windows were not killed" [ "$i" -lt 50 ] || fail "tagged windows were not killed"
exit_mode q exit_mode q
# --- killing the mode's own session -----------------------------------------
# Killing the attached session also destroys the pane hosting the tree. The
# confirmation callback must finish without redrawing the destroyed mode.
$TMUX set-option -g detach-on-destroy off || exit 1
$TMUX choose-tree -s -t aaa:0 -F 'G7' -O name || exit 1
wait_count ': G7' 2
$TMUX send-keys -t aaa:0 g x || fail "send-keys session kill failed"
wait_for 'Kill session aaa'
$TMUX send-keys -t aaa:0 y || fail "session kill confirmation failed"
i=0
while [ "$i" -lt 50 ]; do
sessions=$($TMUX list-sessions -F '#{session_name}' 2>/dev/null)
client_session=$($TMUX list-clients -F '#{client_session}' 2>/dev/null)
[ "$sessions" = 'zzz' ] && [ "$client_session" = 'zzz' ] && break
sleep 0.2
i=$((i + 1))
done
[ "$i" -lt 50 ] || fail "killing the tree session killed the server"
exit 0 exit 0

View File

@@ -59,7 +59,13 @@ os.kill(client, signal.SIGTERM)
size(master, 23, 61, 0, 0) size(master, 23, 61, 0, 0)
os.kill(client, signal.SIGWINCH) os.kill(client, signal.SIGWINCH)
os.kill(client, signal.SIGCONT) os.kill(client, signal.SIGCONT)
# macOS drains the controlling terminal when its session leader exits.
# Keep reading output so the client can finish exiting.
for i in range(200): for i in range(200):
try:
os.read(master, 65536)
except OSError:
pass
if os.waitpid(client, os.WNOHANG)[0] != 0: if os.waitpid(client, os.WNOHANG)[0] != 0:
break break
time.sleep(0.05) time.sleep(0.05)

View File

@@ -109,6 +109,7 @@ pane_bg
pane_bottom pane_bottom
pane_command_duration pane_command_duration
pane_command_end_time pane_command_end_time
pane_command_line
pane_command_running pane_command_running
pane_command_start_time pane_command_start_time
pane_command_status pane_command_status

View File

@@ -1,88 +0,0 @@
#!/bin/sh
. ./input-common.inc
# Exercise expansion by VS16, a skin tone modifier and a regional indicator.
for kind in variation modifier flag; do
case "$kind" in
variation)
combine_seq='\342\234\224\357\270\217'
text='✔️'
;;
modifier)
combine_seq='\342\230\235\360\237\217\273'
text='☝🏻'
;;
flag)
combine_seq='\360\237\207\254\360\237\207\247'
text='🇬🇧'
;;
esac
start_pane "$kind-overwrite" 10 3 "A\347\225\214BC\r${combine_seq}X"
check_capture "$kind-overwrite" "${text}XBC"
check_cursor "$kind-overwrite" '3,0'
check_raw_matches "$kind-overwrite" \
"C 0,0 data=\(2,[0-9]+,$text\) flags=NONE\[0\]" \
'C 0,1 .*flags=PADDING\[[0-9a-f]+\]' \
'C 0,2 data=\(1,1,X\) flags=NONE\[0\]'
start_pane "$kind-edge" 5 3 "abcd${combine_seq}X"
check_capture "$kind-edge" "abcd
${text}X"
check_cursor "$kind-edge" '3,1'
check_raw_matches "$kind-edge" \
'L 0 \(0\) flags=.*WRAPPED' \
'C 0,4 data=\(1,1, \)' \
"C 1,0 data=\(2,[0-9]+,$text\) flags=NONE\[0\]" \
'C 1,1 .*flags=PADDING\[[0-9a-f]+\]' \
'C 1,2 data=\(1,1,X\) flags=NONE\[0\]'
start_pane "$kind-fit" 5 3 "abc${combine_seq}X"
check_capture "$kind-fit" "abc${text}
X"
check_cursor "$kind-fit" '1,1'
start_pane "$kind-scroll" 5 2 "11111\r\nabcd${combine_seq}X"
check_capture "$kind-scroll" "abcd
${text}X"
check_cursor "$kind-scroll" '3,1'
start_pane "$kind-region" 5 4 \
"top\033[2;1Hold\033[4;1Hend\033[2;3r\033[3;1Habcd${combine_seq}X"
check_capture "$kind-region" "top
abcd
${text}X
end"
check_cursor "$kind-region" '3,2'
start_pane "$kind-insert" 10 3 "ABCDE\r\033[4h${combine_seq}X"
check_capture "$kind-insert" "${text}XABCDE"
check_cursor "$kind-insert" '3,0'
start_pane "$kind-insert-edge" 5 3 \
"\033[2;1HABCDE\033[1;1Habcd\033[4h${combine_seq}X"
check_capture "$kind-insert-edge" "abcd
${text}XAB"
check_cursor "$kind-insert-edge" '3,1'
start_pane "$kind-nowrap" 5 3 "\033[?7labc${combine_seq}"
check_capture "$kind-nowrap" "abc${text}"
check_cursor "$kind-nowrap" '4,0'
check_raw_matches "$kind-nowrap" \
"C 0,3 data=\(2,[0-9]+,$text\) flags=NONE\[0\]" \
'C 0,4 .*flags=PADDING\[[0-9a-f]+\]'
done
# A two-column cluster cannot fit in a one-column pane. Keep its narrow base.
start_pane one-column 1 3 '\342\234\224\357\270\217'
check_capture one-column '✔'
check_cursor one-column '1,0'
check_raw_no_matches one-column 'data=\(2,'
# Without wrapping, even a wide cell filling the pane leaves the cursor inside.
start_pane two-columns-nowrap 2 3 '\033[?7l\342\234\224\357\270\217'
check_capture two-columns-nowrap '✔️'
check_cursor two-columns-nowrap '1,0'
exit $exit_status

142
regress/input-osc133.sh Normal file
View File

@@ -0,0 +1,142 @@
#!/bin/sh
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
trap '$TMUX kill-server 2>/dev/null' 0
trap 'exit 1' 1 2 15
check()
{
format=$1
expected=$2
actual=$($TMUX -u display-message -p -t "$pane" "$format") || exit 1
if [ "$actual" != "$expected" ]; then
printf 'format: %s\nexpected: %s\nactual: %s\n' \
"$format" "$expected" "$actual"
exit 1
fi
}
emit()
{
printf '\033]133;%s\033\\' "$1" |
$TMUX display-message -I -t "$pane" || exit 1
}
check_command()
{
emit "C;$1"
check '#{pane_command_line}' "$2"
check '#{@started}' "$2"
check '#{pane_command_running}:#{pane_command_status}' '1:'
emit 'D;7'
check '#{pane_command_line}' "$2"
check '#{@finished}' "$2"
check '#{pane_command_running}:#{pane_command_status}' '0:7'
}
$TMUX new-session -d -s test 'sleep 1000' || exit 1
pane=$($TMUX new-window -d -P -F '#{pane_id}' '') || exit 1
other=$($TMUX new-window -d -P -F '#{pane_id}' '') || exit 1
$TMUX set-hook -g pane-command-started \
'set -gF @started "#{hook_command_line}"' || exit 1
$TMUX set-hook -g pane-command-finished \
'set -gF @finished "#{hook_command_line}"' || exit 1
check '#{pane_command_line}:#{pane_command_running}' ':0'
check_command 'cmdline_url=sleep%201' 'sleep 1'
# Prompt markers leave the most recent command available.
emit A
emit B
check '#{pane_command_line}' 'sleep 1'
$TMUX display-message -p -t "$other" '#{pane_command_line}' |
grep -qx '' || exit 1
# Unknown fields are ignored and decoded semicolons belong to the value.
check_command 'other=1;cmdline_url=echo%20one%3B%20echo%20two;other=2' \
'echo one; echo two'
check_command 'cmdline_url=echo%20a+b%2Bc%25%3d%3D' 'echo a+b+c%=='
check_command 'cmdline_url=echo%20%C3%A9%E4%B8%AD' 'echo é中'
check_command 'cmdline_url=echo%20%2520' 'echo %20'
# Use the same cleanup as titles for control characters and format jobs.
check_command 'cmdline_url=echo%20%23%28date%29' 'echo _(date)'
check_command 'cmdline_url=echo%20%5C' 'echo \\'
# Multiline commands retain tabs and newlines as visible escapes, including
# in the event payloads used by hooks.
check_command 'cmdline_url=sleep%201%0Aecho%20done' 'sleep 1\necho done'
check_command 'cmdline_url=echo%09hello%0Aworld' 'echo\thello\nworld'
# The name validator also permits these bytes before clean_name escapes them.
name=$(printf 'tab\tnewline\nname')
$TMUX rename-window -t "$other" "$name" || exit 1
actual=$($TMUX -u display-message -p -t "$other" '#{window_name}') || exit 1
[ "$actual" = 'tab\tnewline\nname' ] || exit 1
$TMUX select-pane -t "$pane" -T "$name" || exit 1
check '#{pane_title}' 'tab\tnewline\nname'
# Every new start clears old metadata if it is missing or invalid.
emit C
check '#{pane_command_line}:#{@started}' ':'
for metadata in \
'cmdline_url=' \
'other=cmdline_url=stale' \
'xcmdline_url=stale' \
'cmdline_url=bad%' \
'cmdline_url=bad%2' \
'cmdline_url=bad%GG' \
'cmdline_url=bad%00tail' \
'cmdline_url=bad%0Dtail' \
'cmdline_url=bad%1Btail' \
'cmdline_url=bad%07tail' \
'cmdline_url=bad%08tail' \
'cmdline_url=bad%0Btail' \
'cmdline_url=bad%0Ctail' \
'cmdline_url=bad%7Ftail' \
'cmdline_url=bad%FF' \
'cmdline_url=bad%C3' \
'cmdline_url=bad%C0%AF'
do
check_command 'cmdline_url=previous' previous
check_command "$metadata" ''
done
# The limit applies to decoded bytes, not the size of the OSC string.
decoded=$(printf '%5000s' '' | tr ' ' x)
check_command "cmdline_url=$decoded" "$decoded"
encoded=$(awk 'BEGIN { for (i = 0; i < 16384; i++) printf "%%78" }')
decoded=$(printf '%16384s' '' | tr ' ' x)
check_command "cmdline_url=$encoded" "$decoded"
check_command "cmdline_url=${encoded}%78" ''
# Complete UTF-8 characters may reach the limit, but overlong lines are
# rejected in full rather than truncated, even through a character.
check_boundary()
{
prefix=$(printf "%${1}s" '' | tr ' ' x)
check_command "cmdline_url=$prefix$2" "$prefix$3"
check_command "cmdline_url=${prefix}x$2" ''
}
check_boundary 16382 '%C3%A9' 'é'
check_boundary 16381 '%E4%B8%AD' '中'
check_boundary 16380 '%F0%9F%98%80' '😀'
# Each hook gets a snapshot, even if multiple commands arrive in one read.
$TMUX set -g @log '' || exit 1
$TMUX set-hook -g pane-command-started \
'set -gF @log "#{@log}|start:#{hook_command_line}"' || exit 1
$TMUX set-hook -g pane-command-finished \
'set -gF @log "#{@log}|end:#{hook_command_line}:#{hook_command_status}"' || exit 1
printf '\033]133;C;cmdline_url=first\007\033]133;D;0\007\033]133;C;cmdline_url=second\007\033]133;D;1\007\033]133;C\007\033]133;D;2\007' |
$TMUX display-message -I -t "$pane" || exit 1
check '#{@log}' '|start:first|end:first:0|start:second|end:second:1|start:|end::2'
check '#{pane_command_line}' ''
exit 0

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: screen-write.c,v 1.299 2026/10/05 08:41:23 nicm Exp $ */ /* $OpenBSD: screen-write.c,v 1.300 2026/10/12 18:47:52 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -34,10 +34,6 @@ static void screen_write_collect_clear(struct screen_write_ctx *, u_int,
static void screen_write_collect_scroll(struct screen_write_ctx *, u_int); static void screen_write_collect_scroll(struct screen_write_ctx *, u_int);
static void screen_write_collect_flush(struct screen_write_ctx *, int, static void screen_write_collect_flush(struct screen_write_ctx *, int,
const char *); const char *);
#ifdef ENABLE_IMAGES
static void screen_write_image_damage(struct screen_write_ctx *, u_int,
u_int, u_int, u_int);
#endif
static int screen_write_overwrite(struct screen_write_ctx *, static int screen_write_overwrite(struct screen_write_ctx *,
struct grid_cell *, u_int); struct grid_cell *, u_int);
static int screen_write_combine(struct screen_write_ctx *, static int screen_write_combine(struct screen_write_ctx *,
@@ -396,20 +392,6 @@ screen_write_init(struct screen_write_ctx *ctx, struct screen *s)
ctx->bg = 8; ctx->bg = 8;
} }
#ifdef ENABLE_IMAGES
/* Damage temporal images when application input writes text cells. */
static void
screen_write_image_damage(struct screen_write_ctx *ctx, u_int x, u_int y,
u_int sx, u_int sy)
{
image_redraw_area(ctx, x, y, sx, sy);
if (ctx->flags & SCREEN_WRITE_INPUT) {
image_grid_remove_overwritten_spans(ctx->s->grid, x,
ctx->s->grid->hsize + y, sx, sy);
}
}
#endif
/* Initialize writing with a pane. */ /* Initialize writing with a pane. */
void void
screen_write_start_pane(struct screen_write_ctx *ctx, struct window_pane *wp, screen_write_start_pane(struct screen_write_ctx *ctx, struct window_pane *wp,
@@ -1491,11 +1473,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
utf8_set(&gc.data, 'E'); utf8_set(&gc.data, 'E');
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
image_redraw_all(ctx); image_damage_screen(ctx, 0, 0, screen_size_x(s), screen_size_y(s));
if (ctx->flags & SCREEN_WRITE_INPUT) {
image_grid_remove_overwritten_spans(s->grid, 0, s->grid->hsize,
screen_size_x(s), screen_size_y(s));
}
#endif #endif
for (yy = 0; yy < screen_size_y(s); yy++) { for (yy = 0; yy < screen_size_y(s); yy++) {
@@ -1540,13 +1518,13 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1) if (s->cx > screen_size_x(s) - 1)
return; return;
screen_write_initctx(ctx, &ttyctx, 0, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1); image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif #endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
ttyctx.bg = bg;
grid_view_insert_cells(s->grid, s->cx, s->cy, nx, bg); grid_view_insert_cells(s->grid, s->cx, s->cy, nx, bg);
screen_write_collect_flush(ctx, 0, __func__); screen_write_collect_flush(ctx, 0, __func__);
@@ -1580,13 +1558,13 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1) if (s->cx > screen_size_x(s) - 1)
return; return;
screen_write_initctx(ctx, &ttyctx, 0, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1); image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif #endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
ttyctx.bg = bg;
grid_view_delete_cells(s->grid, s->cx, s->cy, nx, bg); grid_view_delete_cells(s->grid, s->cx, s->cy, nx, bg);
screen_write_collect_flush(ctx, 0, __func__); screen_write_collect_flush(ctx, 0, __func__);
@@ -1620,13 +1598,13 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1) if (s->cx > screen_size_x(s) - 1)
return; return;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1); screen_write_initctx(ctx, &ttyctx, 0, 1);
ttyctx.bg = bg; ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, s->cx, s->cy, nx, 1);
#endif
grid_view_clear(s->grid, s->cx, s->cy, nx, 1, bg); grid_view_clear(s->grid, s->cx, s->cy, nx, 1, bg);
screen_write_collect_flush(ctx, 0, __func__); screen_write_collect_flush(ctx, 0, __func__);
@@ -1654,10 +1632,6 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
if (ny == 0) if (ny == 0)
ny = 1; ny = 1;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
if (s->cy < s->rupper || s->cy > s->rlower) { if (s->cy < s->rupper || s->cy > s->rlower) {
if (ny > sy - s->cy) if (ny > sy - s->cy)
ny = sy - s->cy; ny = sy - s->cy;
@@ -1667,6 +1641,10 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
screen_write_initctx(ctx, &ttyctx, 1, 1); screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg; ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
grid_view_insert_lines(gd, s->cy, ny, bg); grid_view_insert_lines(gd, s->cy, ny, bg);
screen_write_collect_flush(ctx, 0, __func__); screen_write_collect_flush(ctx, 0, __func__);
@@ -1691,6 +1669,11 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
screen_write_initctx(ctx, &ttyctx, 1, 1); screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg; ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s),
s->rlower + 1 - s->cy);
#endif
if (s->cy < s->rupper || s->cy > s->rlower) if (s->cy < s->rupper || s->cy > s->rlower)
grid_view_insert_lines(gd, s->cy, ny, bg); grid_view_insert_lines(gd, s->cy, ny, bg);
else else
@@ -1721,10 +1704,6 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
if (ny == 0) if (ny == 0)
ny = 1; ny = 1;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
if (s->cy < s->rupper || s->cy > s->rlower) { if (s->cy < s->rupper || s->cy > s->rlower) {
if (ny > sy - s->cy) if (ny > sy - s->cy)
ny = sy - s->cy; ny = sy - s->cy;
@@ -1734,6 +1713,10 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
screen_write_initctx(ctx, &ttyctx, 1, 1); screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg; ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
grid_view_delete_lines(gd, s->cy, ny, bg); grid_view_delete_lines(gd, s->cy, ny, bg);
screen_write_collect_flush(ctx, 0, __func__); screen_write_collect_flush(ctx, 0, __func__);
@@ -1760,6 +1743,10 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
screen_write_initctx(ctx, &ttyctx, 1, 1); screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg; ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), ry);
#endif
if (s->cy < s->rupper || s->cy > s->rlower) if (s->cy < s->rupper || s->cy > s->rlower)
grid_view_delete_lines(gd, s->cy, ny, bg); grid_view_delete_lines(gd, s->cy, ny, bg);
else else
@@ -1790,11 +1777,17 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg)
u_int flags; u_int flags;
gl = grid_get_line(s->grid, s->grid->hsize + s->cy); gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
if (gl->cellsize == 0 && COLOUR_DEFAULT(bg)) if (gl->cellsize == 0 && COLOUR_DEFAULT(bg)) {
#ifdef ENABLE_IMAGES
if (!image_grid_line_has_images(gl))
return;
#else
return; return;
#endif
}
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1); image_redraw_area(ctx, 0, s->cy, sx, 1);
#endif #endif
flags = gl->flags & GRID_LINE_OSC133_FLAGS; flags = gl->flags & GRID_LINE_OSC133_FLAGS;
@@ -1828,11 +1821,20 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg)
} }
gl = grid_get_line(s->grid, s->grid->hsize + s->cy); gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg))) if (s->cx > sx - 1)
return; return;
if (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)) {
#ifdef ENABLE_IMAGES
if (!image_grid_area_has_images(s->grid, s->cx,
s->grid->hsize + s->cy, sx - s->cx, 1))
return;
#else
return;
#endif
}
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1); image_redraw_area(ctx, s->cx, s->cy, sx - s->cx, 1);
#endif #endif
grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg); grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg);
@@ -1858,7 +1860,7 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg)
} }
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1); image_redraw_area(ctx, 0, s->cy, s->cx + 1, 1);
#endif #endif
if (s->cx > sx - 1) if (s->cx > sx - 1)
@@ -1982,11 +1984,7 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
} }
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
if (rlower == screen_size_y(s) - 1) image_redraw_scroll(ctx);
image_redraw_scroll(ctx);
else
image_redraw_area(ctx, 0, rupper, screen_size_x(s),
rlower - rupper);
#endif #endif
grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
@@ -2080,13 +2078,15 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r; struct visible_ranges *r;
struct visible_range *ri; struct visible_range *ri;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1); screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg; ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
if (s->cx < sx)
image_redraw_area(ctx, s->cx, s->cy, sx - s->cx, 1);
image_redraw_area(ctx, 0, s->cy + 1, sx, sy - (s->cy + 1));
#endif
/* Scroll into history if it is enabled and clearing entire screen. */ /* Scroll into history if it is enabled and clearing entire screen. */
if (s->cx == 0 && if (s->cx == 0 &&
s->cy == 0 && s->cy == 0 &&
@@ -2160,13 +2160,14 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r; struct visible_ranges *r;
struct visible_range *ri; struct visible_range *ri;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, 0, screen_size_x(s), s->cy + 1);
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1); screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg; ttyctx.bg = bg;
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, 0, 0, sx, s->cy);
image_redraw_area(ctx, 0, s->cy, s->cx + 1, 1);
#endif
if (s->cy > 0) if (s->cy > 0)
grid_view_clear(s->grid, 0, 0, sx, s->cy, bg); grid_view_clear(s->grid, 0, 0, sx, s->cy, bg);
if (s->cx > sx - 1) if (s->cx > sx - 1)
@@ -2232,13 +2233,13 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r; struct visible_ranges *r;
struct visible_range *ri; struct visible_range *ri;
screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg;
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
image_redraw_all(ctx); image_redraw_all(ctx);
#endif #endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
ttyctx.bg = bg;
/* Scroll into history if it is enabled. */ /* Scroll into history if it is enabled. */
if ((s->grid->flags & GRID_HISTORY) && if ((s->grid->flags & GRID_HISTORY) &&
ctx->wp != NULL && ctx->wp != NULL &&
@@ -2662,14 +2663,19 @@ screen_write_collect_insert_clear(struct screen_write_ctx *ctx, u_int px,
* background so the columns it covered do not lose their colour. * background so the columns it covered do not lose their colour.
*/ */
static void static void
screen_write_clear_cell(struct grid *gd, u_int px, u_int py) screen_write_clear_cell(struct screen_write_ctx *ctx, u_int px, u_int py)
{ {
struct grid *gd = ctx->s->grid;
struct grid_cell gc; struct grid_cell gc;
int bg; int bg;
grid_view_get_cell(gd, px, py, &gc); grid_view_get_cell(gd, px, py, &gc);
bg = gc.bg; bg = gc.bg;
#ifdef ENABLE_IMAGES
image_damage_screen(ctx, px, py, 1, 1);
#endif
memcpy(&gc, &grid_default_cell, sizeof gc); memcpy(&gc, &grid_default_cell, sizeof gc);
gc.bg = bg; gc.bg = bg;
grid_view_set_cell(gd, px, py, &gc); grid_view_set_cell(gd, px, py, &gc);
@@ -2728,7 +2734,7 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
grid_view_get_cell(s->grid, xx, s->cy, &gc); grid_view_get_cell(s->grid, xx, s->cy, &gc);
if (~gc.flags & GRID_FLAG_PADDING) if (~gc.flags & GRID_FLAG_PADDING)
break; break;
screen_write_clear_cell(s->grid, xx, s->cy); screen_write_clear_cell(ctx, xx, s->cy);
log_debug("%s: padding erased (before) at %u (cx %u)", log_debug("%s: padding erased (before) at %u (cx %u)",
__func__, xx, s->cx); __func__, xx, s->cx);
} }
@@ -2737,7 +2743,7 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
grid_view_get_cell(s->grid, 0, s->cy, &gc); grid_view_get_cell(s->grid, 0, s->cy, &gc);
if (gc.data.width > 1 || if (gc.data.width > 1 ||
(gc.flags & GRID_FLAG_PADDING)) { (gc.flags & GRID_FLAG_PADDING)) {
screen_write_clear_cell(s->grid, xx, s->cy); screen_write_clear_cell(ctx, xx, s->cy);
log_debug("%s: padding erased (before) at %u " log_debug("%s: padding erased (before) at %u "
"(cx %u)", __func__, xx, s->cx); "(cx %u)", __func__, xx, s->cx);
} }
@@ -2747,7 +2753,7 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
} }
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
screen_write_image_damage(ctx, s->cx, s->cy, ci->used, 1); image_damage_screen(ctx, s->cx, s->cy, ci->used, 1);
#endif #endif
grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x, grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x,
@@ -2760,7 +2766,7 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
grid_view_get_cell(s->grid, xx, s->cy, &gc); grid_view_get_cell(s->grid, xx, s->cy, &gc);
if (~gc.flags & GRID_FLAG_PADDING) if (~gc.flags & GRID_FLAG_PADDING)
break; break;
screen_write_clear_cell(s->grid, xx, s->cy); screen_write_clear_cell(ctx, xx, s->cy);
log_debug("%s: padding erased (after) at %u (cx %u)", __func__, log_debug("%s: padding erased (after) at %u (cx %u)", __func__,
xx, s->cx); xx, s->cx);
} }
@@ -2883,8 +2889,8 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
screen_write_initctx(ctx, &ttyctx, 0, 0); screen_write_initctx(ctx, &ttyctx, 0, 0);
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
/* Input semantics decide which image layers this text damages. */ /* Mark images as damaged. */
screen_write_image_damage(ctx, s->cx, s->cy, width, 1); image_damage_screen(ctx, s->cx, s->cy, width, 1);
#endif #endif
/* Handle overwriting of UTF-8 characters. */ /* Handle overwriting of UTF-8 characters. */
@@ -3118,10 +3124,12 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
} else } else
force_wide = 0; force_wide = 0;
/* Set the new cell. */
#ifdef ENABLE_IMAGES #ifdef ENABLE_IMAGES
screen_write_image_damage(ctx, cx - n, cy, n, 1); /* Mark images as damaged. */
image_damage_screen(ctx, cx - n, cy, n, 1);
#endif #endif
/* Set the new cell. */
grid_view_set_cell(gd, cx - n, cy, &last); grid_view_set_cell(gd, cx - n, cy, &last);
if (force_wide) if (force_wide)
grid_view_set_padding(gd, cx - 1, cy, last.bg); grid_view_set_padding(gd, cx - 1, cy, last.bg);
@@ -3195,12 +3203,12 @@ screen_write_overwrite(struct screen_write_ctx *ctx, struct grid_cell *gc,
if (~tmp_gc.flags & GRID_FLAG_PADDING) if (~tmp_gc.flags & GRID_FLAG_PADDING)
break; break;
log_debug("%s: padding at %u,%u", __func__, xx, s->cy); log_debug("%s: padding at %u,%u", __func__, xx, s->cy);
screen_write_clear_cell(gd, xx, s->cy); screen_write_clear_cell(ctx, xx, s->cy);
} }
/* Overwrite the character at the start of this padding. */ /* Overwrite the character at the start of this padding. */
log_debug("%s: character at %u,%u", __func__, xx, s->cy); log_debug("%s: character at %u,%u", __func__, xx, s->cy);
screen_write_clear_cell(gd, xx, s->cy); screen_write_clear_cell(ctx, xx, s->cy);
done = 1; done = 1;
} }
@@ -3218,7 +3226,7 @@ screen_write_overwrite(struct screen_write_ctx *ctx, struct grid_cell *gc,
break; break;
log_debug("%s: overwrite at %u,%u", __func__, xx, log_debug("%s: overwrite at %u,%u", __func__, xx,
s->cy); s->cy);
screen_write_clear_cell(gd, xx, s->cy); screen_write_clear_cell(ctx, xx, s->cy);
done = 1; done = 1;
} }
} }
@@ -3257,24 +3265,6 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len,
tty_write(tty_cmd_rawstring, &ttyctx); tty_write(tty_cmd_rawstring, &ttyctx);
} }
#ifdef ENABLE_IMAGES
/* Write a SIXEL image. */
void
screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
u_int bg)
{
struct image *im;
im = sixel_to_image(si);
if (im == NULL) {
sixel_free(si);
return;
}
image_write_sixel(ctx, im, bg);
image_free(image_get_id(im));
}
#endif
/* Turn alternate screen on. */ /* Turn alternate screen on. */
void void
screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,

254
tmux-image.h Normal file
View File

@@ -0,0 +1,254 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef TMUX_IMAGE_H
#define TMUX_IMAGE_H
#ifdef ENABLE_IMAGES
struct grid;
struct grid_line;
struct image;
struct image_line;
struct image_store;
struct screen;
struct screen_write_ctx;
struct sixel_image;
struct tty;
struct grid_cell;
struct image_rect;
struct image_backend;
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
#define IMAGE_BACKEND_GRAPHICAL 0x1
#define IMAGE_BACKEND_SCROLLS 0x2
#define IMAGE_BACKEND_CLIPPED 0x4
/* Private ACS keys used by the text image backend. */
#define TTY_ACS_IMAGE_SHADE_LIGHT 0x80
#define TTY_ACS_IMAGE_SHADE_MEDIUM 0x81
#define TTY_ACS_IMAGE_SHADE_DARK 0x82
#define TTY_ACS_IMAGE_BLOCK 0x83
#define TTY_ACS_IMAGE_HALF_UPPER 0x84
#define TTY_ACS_IMAGE_HALF_LOWER 0x85
#define TTY_ACS_IMAGE_HALF_LEFT 0x86
#define TTY_ACS_IMAGE_HALF_RIGHT 0x87
#define TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT 0x88
#define TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT 0x89
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT 0x8a
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT 0x8b
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT 0x8c
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT 0x8d
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT 0x8e
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT 0x8f
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT 0x90
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT 0x91
#define TTY_ACS_IMAGE_SEXTANT_FIRST 0x92
#define TTY_ACS_IMAGE_SEXTANT_LAST 0xcd
/* Averaged RGBA colour and brightness used by the text image backend. */
struct image_sample {
u_char red;
u_char green;
u_char blue;
u_char alpha;
u_char brightness;
};
#define IMAGE_SAMPLE_COLUMNS 2
#define IMAGE_SAMPLE_ROWS 6
/* Whole-cell and subcell samples generated lazily for one image cell. */
struct image_cell {
struct image_sample whole;
struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS];
};
enum kitty_parse_status {
KITTY_PARSE_ERROR = -1,
KITTY_PARSE_OK,
KITTY_PARSE_MORE,
KITTY_PARSE_MISSING,
KITTY_PARSE_UNSUPPORTED
};
/* Reply fields and placement actions returned by one Kitty command. */
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;
enum kitty_parse_status status;
};
/* Resolved source cell and application IDs of one Unicode placeholder. */
struct kitty_placeholder {
struct image *image;
u_int source_x;
u_int source_y;
u_int image_id;
u_int placement_id;
int32_t z;
};
/* View of all or part of an image. */
struct image_view {
/* Position and size in the image, in pixels. */
u_int x;
u_int y;
u_int width;
u_int height;
/* Size after scaling, in pixels. */
u_int scaled_width;
u_int scaled_height;
/* Destination canvas size and offset in pixels. */
u_int canvas_width;
u_int canvas_height;
u_int canvas_xoff;
u_int canvas_yoff;
/* Destination size in terminal cells. */
u_int sx;
u_int sy;
};
/* image.c */
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 *, struct image_view *);
struct image *image_find(u_int);
u_int image_get_id(struct image *);
void image_ref(struct image *);
void image_free(struct image *);
void image_get_size(struct image *, u_int *, u_int *);
void image_get_canvas_size(struct image *, u_int *, u_int *);
void image_get_size_in_cells(struct image *, u_int *, u_int *);
const u_char *image_get_pixels(struct image *, size_t *, size_t *);
void image_size_in_cells(u_int, u_int, u_int, u_int, u_int *,
u_int *);
void image_set_no_cursor(struct image *);
int image_has_no_cursor(struct image *);
struct sixel_image *image_get_sixel(struct image *);
void image_set_sixel(struct image *, struct sixel_image *);
u_int image_get_source_id(struct image *);
u_int image_get_order_id(struct image *);
void image_set_order_id(struct image *, u_int);
void *image_get_fallback_data(struct image *);
void image_set_fallback_data(struct image *, void *);
const struct image_cell *image_get_cell(struct image *, u_int, u_int);
void image_get_pixel_rect(struct image *, u_int, u_int, u_int,
u_int, u_int *, u_int *, u_int *, u_int *);
u_char *image_base64_decode(const char *, size_t, size_t, size_t *);
u_char *image_png_decode(const u_char *, size_t, size_t, u_int *,
u_int *);
/* image-grid.c */
void image_grid_add(struct grid *, struct image *, u_int, u_int,
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_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,
u_int);
void image_grid_duplicate_lines(struct grid *, u_int, struct grid *,
u_int, u_int);
int image_grid_line_has_images(struct grid_line *);
int image_grid_area_has_images(struct grid *, u_int, u_int, u_int,
u_int);
void image_redraw_area(struct screen_write_ctx *, u_int, u_int,
u_int, u_int);
void image_redraw_all(struct screen_write_ctx *);
void image_redraw_scroll(struct screen_write_ctx *);
void image_damage_screen(struct screen_write_ctx *, u_int, u_int,
u_int, u_int);
void image_grid_copy_area(struct grid *, u_int, u_int, struct grid *,
u_int, u_int, u_int, u_int);
void image_grid_resize_width(struct grid *, u_int);
int image_grid_has_image(struct grid *, 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);
void image_write_sixel(struct screen_write_ctx *, struct image *, u_int);
void image_write_kitty(struct screen_write_ctx *, struct image *, u_int,
u_int, u_int, int32_t);
void image_clear(struct screen_write_ctx *, u_int);
void image_clear_kitty(struct screen_write_ctx *,
const struct kitty_parse_result *);
int image_get_fallback_at(struct tty *, struct screen *, u_int, u_int,
const struct grid_cell *, struct grid_cell *);
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 *);
int64_t image_rect_get_z(const struct image_rect *);
void image_draw_line(struct tty *, struct screen *, u_int, u_int, u_int,
u_int, u_int, int);
void image_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void image_redraw_finish(struct tty *);
void image_draw_flush(struct tty *);
int image_backend_flags(struct tty *);
int image_tty_update(struct tty *);
void image_tty_geometry_changed(struct tty *);
void image_tty_free(struct tty *, int);
/* image-fallback.c */
void fallback_get_cell(struct tty *, struct image *, u_int, u_int,
const struct grid_cell *, struct grid_cell *);
void fallback_free(struct image *);
/* image-kitty.c */
struct image *kitty_parse_image(void **, const u_char *, size_t, u_int,
u_int, struct kitty_parse_result *);
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 *);
void kitty_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void kitty_redraw_finish(struct tty *);
void kitty_free_output_state(struct tty *, int);
/* image-sixel.c */
#define SIXEL_COLOUR_REGISTERS 1024
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int,
u_int);
void sixel_write(struct screen_write_ctx *, struct sixel_image *,
u_int);
void sixel_draw_rect(struct tty *, const struct image_rect *);
void sixel_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void sixel_free_output(struct tty *, int);
void sixel_flush_output(struct tty *);
struct image *sixel_to_image(struct sixel_image *);
void sixel_free(struct sixel_image *);
void sixel_log(struct sixel_image *);
#endif /* ENABLE_IMAGES */
#endif /* TMUX_IMAGE_H */

6
tmux.1
View File

@@ -1,4 +1,4 @@
.\" $OpenBSD: tmux.1,v 1.1184 2026/10/12 12:35:03 nicm Exp $ .\" $OpenBSD: tmux.1,v 1.1185 2026/10/13 10:21:58 nicm Exp $
.\" .\"
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> .\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
.\" .\"
@@ -14,7 +14,7 @@
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\" .\"
.Dd $Mdocdate: October 12 2026 $ .Dd $Mdocdate: October 13 2026 $
.Dt TMUX 1 .Dt TMUX 1
.Os .Os
.Sh NAME .Sh NAME
@@ -7463,6 +7463,7 @@ The following variables are available, where appropriate:
.It Li "history_size" Ta "" Ta "Size of history in lines" .It Li "history_size" Ta "" Ta "Size of history in lines"
.It Li "hook" Ta "" Ta "Name of running hook, if any" .It Li "hook" Ta "" Ta "Name of running hook, if any"
.It Li "hook_client" Ta "" Ta "Name of client where hook was run, if any" .It Li "hook_client" Ta "" Ta "Name of client where hook was run, if any"
.It Li "hook_command_line" Ta "" Ta "Command line reported for an OSC 133 command hook, if any"
.It Li "hook_fire_count" Ta "" Ta "Number of times hook has fired" .It Li "hook_fire_count" Ta "" Ta "Number of times hook has fired"
.It Li "hook_fire_time" Ta "" Ta "Time hook last fired" .It Li "hook_fire_time" Ta "" Ta "Time hook last fired"
.It Li "hook_last" Ta "" Ta "Previous value for a monitor hook" .It Li "hook_last" Ta "" Ta "Previous value for a monitor hook"
@@ -7531,6 +7532,7 @@ The following variables are available, where appropriate:
.It Li "pane_bottom" Ta "" Ta "Bottom of pane" .It Li "pane_bottom" Ta "" Ta "Bottom of pane"
.It Li "pane_command_duration" Ta "" Ta "Current or most recent OSC 133 command duration in seconds" .It Li "pane_command_duration" Ta "" Ta "Current or most recent OSC 133 command duration in seconds"
.It Li "pane_command_end_time" Ta "" Ta "Time most recent OSC 133 command ended" .It Li "pane_command_end_time" Ta "" Ta "Time most recent OSC 133 command ended"
.It Li "pane_command_line" Ta "" Ta "Current or most recent OSC 133 command line, if reported"
.It Li "pane_command_running" Ta "" Ta "1 if an OSC 133 command is running" .It Li "pane_command_running" Ta "" Ta "1 if an OSC 133 command is running"
.It Li "pane_command_start_time" Ta "" Ta "Time current or most recent OSC 133 command started" .It Li "pane_command_start_time" Ta "" Ta "Time current or most recent OSC 133 command started"
.It Li "pane_command_status" Ta "" Ta "Exit status from most recent OSC 133 command" .It Li "pane_command_status" Ta "" Ta "Exit status from most recent OSC 133 command"

245
tmux.h
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux.h,v 1.1457 2026/10/12 12:35:03 nicm Exp $ */ /* $OpenBSD: tmux.h,v 1.1459 2026/10/13 10:21:58 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -19,11 +19,13 @@
#ifndef TMUX_H #ifndef TMUX_H
#define TMUX_H #define TMUX_H
#include <sys/types.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/uio.h> #include <sys/uio.h>
#include <limits.h> #include <limits.h>
#include <stdarg.h> #include <stdarg.h>
#include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <termios.h> #include <termios.h>
#include <wchar.h> #include <wchar.h>
@@ -33,6 +35,7 @@
#endif #endif
#include "compat.h" #include "compat.h"
#include "tmux-image.h"
#include "tmux-protocol.h" #include "tmux-protocol.h"
#include "xmalloc.h" #include "xmalloc.h"
@@ -77,15 +80,6 @@ struct screen_write_cline;
struct screen_write_ctx; struct screen_write_ctx;
struct session; struct session;
#ifdef ENABLE_IMAGES
struct image;
struct image_backend;
struct image_rect;
struct image_line;
struct image_store;
struct sixel_image;
#endif
struct tty_ctx; struct tty_ctx;
struct tty_code; struct tty_code;
struct tty_key; struct tty_key;
@@ -1048,123 +1042,6 @@ struct style {
u_int link; u_int link;
}; };
#ifdef ENABLE_IMAGES
/* Private ACS keys used by the text image backend. */
#define TTY_ACS_IMAGE_SHADE_LIGHT 0x80
#define TTY_ACS_IMAGE_SHADE_MEDIUM 0x81
#define TTY_ACS_IMAGE_SHADE_DARK 0x82
#define TTY_ACS_IMAGE_BLOCK 0x83
#define TTY_ACS_IMAGE_HALF_UPPER 0x84
#define TTY_ACS_IMAGE_HALF_LOWER 0x85
#define TTY_ACS_IMAGE_HALF_LEFT 0x86
#define TTY_ACS_IMAGE_HALF_RIGHT 0x87
#define TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT 0x88
#define TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT 0x89
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT 0x8a
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT 0x8b
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT 0x8c
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT 0x8d
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT 0x8e
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT 0x8f
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT 0x90
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT 0x91
#define TTY_ACS_IMAGE_SEXTANT_FIRST 0x92
#define TTY_ACS_IMAGE_SEXTANT_LAST 0xcd
/* Averaged RGBA colour and brightness used by the text image backend. */
struct image_sample {
u_char red;
u_char green;
u_char blue;
u_char alpha;
u_char brightness;
};
#define IMAGE_SAMPLE_COLUMNS 2
#define IMAGE_SAMPLE_ROWS 6
/* Whole-cell and subcell samples generated lazily for one image cell. */
struct image_cell {
struct image_sample whole;
struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS];
};
/* Immutable pixels and derived output data shared by image placements. */
struct image {
u_int id;
u_int references;
u_int flags;
/* 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;
u_int canvas_width;
u_int canvas_height;
/* Grid dimensions and pixel buffer lengths in bytes. */
u_int sx;
u_int sy;
size_t stride;
size_t size;
u_char *pixels;
struct sixel_image *sixel;
struct image_cell *cells;
void *fallback_data;
RB_ENTRY(image) entry;
};
RB_HEAD(images, image);
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
/* Source crop and padded destination canvas for creating an image view. */
struct image_view {
u_int x; /* Crop origin in source pixels. */
u_int y;
u_int width; /* Crop size in source pixels. */
u_int height;
u_int scaled_width; /* Resampled crop size in pixels. */
u_int scaled_height;
u_int canvas_width; /* Padded canvas size in pixels. */
u_int canvas_height;
u_int sx; /* Placement size in cells. */
u_int sy;
u_int x_offset; /* Crop origin in canvas pixels. */
u_int y_offset;
};
enum kitty_parse_status {
KITTY_PARSE_ERROR = -1,
KITTY_PARSE_OK,
KITTY_PARSE_MORE,
KITTY_PARSE_MISSING,
KITTY_PARSE_UNSUPPORTED
};
/* Reply fields and placement actions returned by one Kitty command. */
struct kitty_parse_result {
u_int image_id;
u_int image_number;
u_int replace_id; /* Old server ID, or zero. */
u_int placement_id;
u_int quiet;
u_int x; /* Deletion coordinates. */
u_int y;
int32_t z;
char action;
char delete;
enum kitty_parse_status status;
};
/* Resolved source cell and application IDs of one Unicode placeholder. */
struct kitty_placeholder {
struct image *image;
u_int source_x; /* Origin in image cells. */
u_int source_y;
u_int image_id;
u_int placement_id;
int32_t z;
};
#endif
/* Cursor style. */ /* Cursor style. */
enum screen_cursor_style { enum screen_cursor_style {
SCREEN_CURSOR_DEFAULT, SCREEN_CURSOR_DEFAULT,
@@ -1493,6 +1370,7 @@ struct window_pane {
time_t cmd_start_time; time_t cmd_start_time;
time_t cmd_end_time; time_t cmd_end_time;
int cmd_status; int cmd_status;
char *cmd_line;
int fd; int fd;
struct bufferevent *event; struct bufferevent *event;
@@ -3764,10 +3642,6 @@ void screen_write_setselection(struct screen_write_ctx *, const char *,
u_char *, u_int); u_char *, u_int);
void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int, void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int,
int); int);
#ifdef ENABLE_IMAGES
void screen_write_sixelimage(struct screen_write_ctx *,
struct sixel_image *, u_int);
#endif
void screen_write_alternateon(struct screen_write_ctx *, void screen_write_alternateon(struct screen_write_ctx *,
struct grid_cell *, int); struct grid_cell *, int);
void screen_write_alternateoff(struct screen_write_ctx *, void screen_write_alternateoff(struct screen_write_ctx *,
@@ -4357,115 +4231,6 @@ void spawn_editor_finish(struct window_pane *);
/* regsub.c */ /* regsub.c */
char *regsub(const char *, const char *, const char *, int); char *regsub(const char *, const char *, const char *, int);
#ifdef ENABLE_IMAGES
/* image.c */
#define IMAGE_BACKEND_GRAPHICAL 0x1
#define IMAGE_BACKEND_SCROLLS 0x2
#define IMAGE_BACKEND_CLIPPED 0x4
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 *, 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 *);
void image_get_canvas_size(const struct image *, u_int *, u_int *);
void image_get_size_in_cells(const struct image *, u_int *, u_int *);
const u_char *image_get_pixels(const struct image *, size_t *, size_t *);
void image_set_no_cursor(struct image *);
struct sixel_image *image_get_sixel(const struct image *);
void image_set_sixel(struct image *, struct sixel_image *);
void image_ref(u_int);
void image_free(u_int);
void image_write_sixel(struct screen_write_ctx *, struct image *,
u_int);
void image_write_kitty(struct screen_write_ctx *, struct image *,
u_int, u_int, u_int, int32_t);
void image_get_pixel_rect(const struct image *, u_int, u_int,
u_int, u_int, u_int *, u_int *, u_int *, u_int *);
void image_size_in_cells(u_int, u_int, u_int, u_int, u_int *,
u_int *);
u_char *image_base64_decode(const char *, size_t, size_t, size_t *);
u_char *image_png_decode(const u_char *, size_t, size_t, u_int *,
u_int *);
void image_redraw_area(struct screen_write_ctx *, u_int, u_int,
u_int, u_int);
void image_redraw_all(struct screen_write_ctx *);
void image_redraw_scroll(struct screen_write_ctx *);
void image_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void image_redraw_finish(struct tty *);
void image_draw_flush(struct tty *);
int image_backend_flags(struct tty *);
int image_tty_update(struct tty *);
void image_tty_geometry_changed(struct tty *);
void image_tty_free(struct tty *, int);
void image_draw_line(struct tty *, struct screen *, u_int, u_int,
u_int, u_int, u_int, int);
const struct image_cell *image_get_cell(struct image *, u_int, u_int);
int image_get_fallback_at(struct tty *, struct screen *, u_int,
u_int, const struct grid_cell *, struct grid_cell *);
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 *);
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 *);
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,
u_int);
void image_grid_duplicate_lines(struct grid *, u_int, struct grid *,
u_int, u_int);
void image_grid_copy_area(struct grid *, u_int, u_int, struct grid *,
u_int, u_int, u_int, u_int);
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_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-fallback.c */
void fallback_get_cell(struct tty *, struct image *, u_int,
u_int, const struct grid_cell *, struct grid_cell *);
void fallback_free(struct image *);
/* image-kitty.c */
struct image *kitty_parse_image(void **, const u_char *, size_t, u_int,
u_int, struct kitty_parse_result *);
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 *);
void kitty_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void kitty_redraw_finish(struct tty *);
void kitty_free_output_state(struct tty *, int);
/* image-sixel.c */
#define SIXEL_COLOUR_REGISTERS 1024
void sixel_draw_rect(struct tty *,
const struct image_rect *);
void sixel_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void sixel_free_output(struct tty *, int);
void sixel_flush_output(struct tty *);
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int,
u_int);
void sixel_free(struct sixel_image *);
void sixel_log(struct sixel_image *);
struct image *sixel_to_image(struct sixel_image *);
#endif
/* server-acl.c */ /* server-acl.c */
#define SERVER_ACL_READONLY 0x1 #define SERVER_ACL_READONLY 0x1
#define SERVER_ACL_IS_GROUP 0x2 #define SERVER_ACL_IS_GROUP 0x2

1
tty.c
View File

@@ -1487,7 +1487,6 @@ tty_check_codeset(struct tty *tty, const struct grid_cell *gc)
return (&new); return (&new);
} }
void void
tty_sync_start(struct tty *tty) tty_sync_start(struct tty *tty)
{ {

4
utf8.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: utf8.c,v 1.74 2026/10/05 08:32:24 nicm Exp $ */ /* $OpenBSD: utf8.c,v 1.75 2026/10/13 10:21:58 nicm Exp $ */
/* /*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -835,7 +835,7 @@ utf8_isvalid(const char *s)
continue; continue;
return (0); return (0);
} }
if (*s < 0x20 || *s > 0x7e) if (*s != '\t' && *s != '\n' && (*s < 0x20 || *s > 0x7e))
return (0); return (0);
s++; s++;
} }

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-tree.c,v 1.96 2026/08/25 07:23:30 nicm Exp $ */ /* $OpenBSD: window-tree.c,v 1.97 2026/10/12 18:23:25 nicm Exp $ */
/* /*
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1344,12 +1344,10 @@ window_tree_kill_current_callback(struct client *c, void *modedata,
if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
return (PROMPT_CLOSE); return (PROMPT_CLOSE);
data->references++;
window_tree_kill_each(data, mode_tree_get_current(mtd), c, KEYC_NONE); window_tree_kill_each(data, mode_tree_get_current(mtd), c, KEYC_NONE);
server_renumber_all(); server_renumber_all();
data->references++;
cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
return (PROMPT_CLOSE); return (PROMPT_CLOSE);
} }
@@ -1365,12 +1363,10 @@ window_tree_kill_tagged_callback(struct client *c, void *modedata,
if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
return (PROMPT_CLOSE); return (PROMPT_CLOSE);
data->references++;
mode_tree_each_tagged(mtd, window_tree_kill_each, c, KEYC_NONE, 1); mode_tree_each_tagged(mtd, window_tree_kill_each, c, KEYC_NONE, 1);
server_renumber_all(); server_renumber_all();
data->references++;
cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); cmdq_append(c, cmdq_get_callback(window_tree_command_done, data));
return (PROMPT_CLOSE); return (PROMPT_CLOSE);
} }
@@ -1453,6 +1449,8 @@ window_tree_key(struct window_mode_entry *wme, struct client *c,
item = mode_tree_get_current(data->data); item = mode_tree_get_current(data->data);
finished = mode_tree_key(data->data, c, &key, m, &x, &y); finished = mode_tree_key(data->data, c, &key, m, &x, &y);
if (data->dead)
return;
again: again:
if (item != (new_item = mode_tree_get_current(data->data))) { if (item != (new_item = mode_tree_get_current(data->data))) {

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window.c,v 1.385 2026/10/08 07:06:26 nicm Exp $ */ /* $OpenBSD: window.c,v 1.386 2026/10/13 10:21:58 nicm Exp $ */
/* /*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1670,6 +1670,7 @@ window_pane_free(struct window_pane *wp)
log_debug("pane %%%u freed (%d references)", wp->id, wp->references); log_debug("pane %%%u freed (%d references)", wp->id, wp->references);
free(wp->searchstr); free(wp->searchstr);
free(wp->cmd_line);
screen_free(&wp->status_screen); screen_free(&wp->status_screen);
screen_free(&wp->base); screen_free(&wp->base);
free(wp->r.ranges); free(wp->r.ranges);